Idempotency keys
A client-supplied key that lets the server safely deduplicate retries of the same logical operation.
Idempotency means doing an operation twice has the same effect as doing it once. Networks fail. Clients retry. Without idempotency, retries double-charge cards, send duplicate webhooks, and create twin invoices.
How it works
Client generates a UUID for the operation. Sends it as Idempotency-Key: 7c9e6679-.... Server stores key + request fingerprint + response. On replay with the same key, return the cached response instead of executing again.
The three states
- in_progress - first call running. Second call must wait, not start new work. Return 409 or block.
- succeeded - return cached response.
- failed - decide policy. Stripe returns the cached failure. Some APIs let you retry.
What to fingerprint
Hash the request body. If the client sends the same key with a different body, that is a bug or a hijack. Return 422. Stripe does this.
Where to store
A dedicated table or Redis. Index by key. TTL of 24 hours is the Stripe default, enough to cover any reasonable retry window.
CREATE TABLE idempotency_keys (
key TEXT PRIMARY KEY,
request_hash TEXT NOT NULL,
response_body JSONB,
status TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now(),
expires_at TIMESTAMPTZ
);The transactional trap
You insert the idempotency row, call Stripe, save the response. If the Stripe call fails and you roll back the transaction, the idempotency row vanishes. The retry now sees no key and charges again.
Fix: commit the idempotency row before the side effect, with in_progress. Then do the work. Then update to succeeded in a second transaction. Or use the outbox pattern.
Which methods need it
- POST - yes, always for anything with side effects.
- PUT, DELETE - already idempotent by HTTP spec, but real implementations are not (delete then create races). Use keys anyway.
- GET - no, reads are safe to retry.
My rule
Every mutating endpoint takes an optional Idempotency-Key. Internal services pass it automatically through HTTP client middleware. External clients are told to send it. Costs almost nothing, prevents an entire category of nightmare bugs.
Learn more
- Article
- Docs
- ArticleAWS: Making retries safe with idempotent APIsAWS Builders Library