Idempotency
What idempotency means precisely, idempotency keys, and the financial-systems pattern.
Overview
Why idempotency matters
An idempotent operation can be performed multiple times with the same result: f(x) = f(f(x)). It is critical for retries, because retrying a non-idempotent operation causes duplicate side effects.
The canonical case is a payment API. A network timeout triggers a client retry; the first request succeeded but its response was lost, so the retry creates a second charge. With an idempotency key, the server detects the duplicate and returns the original response.
> [!IMPORTANT] The industry standard is Stripe-style keys: a UUID per logical operation, sent as Idempotency-Key, stored with the response for 24 hours.
Which operations need keys
PUT and DELETE are naturally idempotent, and GET is by definition. The danger zone is POST for creation: charges, messages, transactions.
- PUT /users/123 with {name: "Alice"} — Yes — Same state
- DELETE /users/123 — Yes — No-op after the first
- GET /users/123 — Yes — Same read
- POST /payments — No — Multiple charges
- POST /tweets — No — Duplicate tweets
- PATCH /users/123/credits +10 — No — Wrong total
The retry problem
When a non-idempotent request fails, you cannot tell which case you are in.
The Stripe-style pattern
- 1 — Client decides on a non-idempotent operation and generates UUID abc-123
- 2 — Client sends POST /payments with header Idempotency-Key: abc-123
- 3 — Server processes and stores (key="abc-123", result=, ttl=24h)
- 4 — Server responds
- 5 — On network failure or timeout, the client retries with the same key
- 6 — Server finds the key and returns the original response without reprocessing
Implementation details
- Dedup key — The idempotency key, a UUID
- Dedup value — Response body plus status code
- TTL — Matches the retry window, typically 24 hours
- Storage — Redis, fast but memory-capped, or a DB, durable
- Concurrency — Two simultaneous requests with one key: the second waits, via a per-key pessimistic lock or "INSERT IF NOT EXISTS"; it then returns the same result or "in progress; come back later"
- Same key, different body — 409 Conflict; the key names one operation and is being misused
- Retry before the first completes — 409 "already in progress; same key"; client polls or waits
- TTL expired — Treated as a fresh request; clients must respect the TTL
Where idempotency lives
- Application — Most common: check Redis or a DB at the start of every non-idempotent handler
- API gateway — Less common; must know which endpoints are non-idempotent
- Client library — The Stripe SDK handles it client-side automatically
Some APIs make idempotency mandatory: AWS's state-machine API, Stripe's payment APIs, and most cloud services' write operations. Others require a client-supplied idempotency token on every write, so non-idempotent operations are impossible by design.
Financial systems pattern
Stripe's docs: "Stripe's API has built-in support for idempotency for safely retrying API requests without accidentally performing the same operation twice."
The exception: distributed sagas
Long-running, multi-step operations need different handling. Each saga step is idempotent and retried from where it failed, while compensation actions handle rollbacks.
> [!TIP] Sagas are more complex than single-call idempotency, but they are what cross-service transactions need.
Key concepts
- Idempotent
- Multiple calls = same result. f(x) = f(f(x)). Required for safe retries.
- Idempotency key
- Client-generated unique ID per operation. Server deduplicates by key. Standard for non-idempotent APIs.
- Dedup table
- Storage of (key, result) for retried operations. Redis (fast) or DB (durable). TTL matches retry window.
- Concurrency on same key
- Second simultaneous request with same key: lock or 409 Conflict. Don't process twice.
- Stripe / AWS API pattern
- Idempotency built into the API. Client provides key; server handles dedup. The standard for write APIs.
- Saga pattern
- Multi-step distributed workflow; each step idempotent; compensations for rollback. For cross-service transactions.
When to use it
- Designing every non-idempotent API — idempotency keys are mandatory
- Reviewing existing retry logic — are retries safe?
- Postmortem with duplicate side effects (double charges, duplicate posts) — was idempotency missing?
- Cross-service workflows — saga pattern with idempotent steps
Interview questions
- Walk me through implementing idempotent payment processing.
- How do you handle idempotency for long-running operations?
- When can you skip idempotency keys?
Sign up free to read the full topic
More SRE topics
Start free on Cariara · Coding problems · System design studio