Skip to content

Idempotency#

The Problem#

You call Stripe to charge a user. Network times out. You don't know if Stripe received the request or not.

Your service → POST /charge → Stripe
              network timeout

Did Stripe get it? Did they charge? Unknown.

If you retry — you might charge the user twice. If you don't retry — the payment might be lost.

Idempotency = the same operation can be performed multiple times and produces the same result as if it was performed once.


Idempotency Key#

A unique identifier sent with every request. The server uses it to detect and ignore duplicate requests.

Client sends:
  POST /charge
  Idempotency-Key: order-uuid-abc123
  amount: 500

Network times out — client retries:
  POST /charge
  Idempotency-Key: order-uuid-abc123  ← same key
  amount: 500

Stripe sees same key → "already processed this"
→ returns same response as first time
→ does NOT charge again ✓

How the server implements it:

First request with key order-uuid-abc123:
  → process charge
  → store result in DB: {key: "order-uuid-abc123", result: "success", charge_id: "ch_123"}
  → return result

Second request with same key:
  → look up key in DB → found
  → return stored result immediately
  → don't touch payment logic at all


Two-Layer Idempotency Protection#

Idempotency needs to be enforced at every hop — not just at the final destination.

Client → Your Service → Stripe

Layer 1 — Client → Your Service:

Client sends Idempotency-Key: order-uuid-abc123
Your service checks its own DB → already processed? → return cached response
Not processed → proceed to call Stripe

Layer 2 — Your Service → Stripe:

Your service forwards same key to Stripe
Stripe checks its own DB → already charged? → return cached response
Not charged → charge and store result

Without both layers:

Your service calls Stripe → Stripe charges → success
Your service crashes before saving result to its own DB
Client retries → your service thinks it's new request
Your service calls Stripe again WITHOUT idempotency key
Stripe charges again → double charge ✗

Both layers protect against their own failure independently.


Which Operations Need Idempotency Keys#

Idempotency keys make non-safe operations safe to retry

HTTP Method Safe to retry? Why
GET ✅ Always Read only — no side effects
PUT ✅ Always Idempotent by definition — same result each time
DELETE ✅ Usually Deleting already-deleted resource returns 404, not duplicate
POST ❌ Not by default Creates new resource each time
POST + idempotency key ✅ Yes Server detects duplicate, returns same result

POST operations that need idempotency keys:

POST /orders          → create order (don't create twice)
POST /payments/charge → charge card (don't charge twice)
POST /emails/send     → send email (don't send twice)
POST /jobs/trigger    → trigger background job (don't run twice)


Idempotency Key Best Practices#

Generated by: client (not server)
Format:       UUID v4 — random, globally unique
Scope:        per operation, not per session
Expiry:       store result for 24-48 hours (balance storage vs safety window)

Good key:   order-uuid-f47ac10b-58cc-4372-a567-0e02b2c3d479
Bad key:    user-123 (not unique per operation)
Bad key:    timestamp (two requests in same millisecond → same key)

Interview framing

"I'd use idempotency keys at every service hop — client sends a UUID with each request, my service checks if it's already processed before proceeding, and forwards the same key downstream to Stripe. This makes retries safe at every layer. Without it, a network timeout on the Stripe call could cause a double charge even with a retry loop."