Idempotency#
The Problem#
You call Stripe to charge a user. Network times out. You don't know if Stripe received the request or not.
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.
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:
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."