Concept · Networking & APIs
Idempotency keys
Make a retried POST do its work exactly once by remembering the client’s request key.
An operation is idempotent if doing it twice has the same effect as doing it once. GET, PUT and DELETE are idempotent by definition; POST — create an order, charge a card, send a message — usually isn't. Yet clients must retry POSTs, because a timeout doesn't tell them whether the server did the work.
An idempotency key fixes that. The client generates a unique key per logical operation and sends it with every attempt:
POST /v1/payments
Idempotency-Key: 5f8a2c1e-8d3b-4e7a-9c55-0b6c1f7a2d90
Content-Type: application/json
{"order_id": "ord_981", "amount": 4200, "currency": "usd"}Server behaviour
- First request with a key: atomically record the key as "in progress" (a unique constraint makes this safe under concurrency), do the work, then store the response with the key.
- Repeat with the same key and body: return the stored response without doing the work again.
- Repeat while the first is still running: return
409 Conflict(or wait), never run it in parallel. - Same key, different body: reject with
422, because it's a client bug.
Keep keys long enough to cover retries — 24 hours is common — and scope them per client or API key so different clients can't collide.
Getting the details right
- Store the key in the same transaction as the effect when the effect is in your database, so a crash can't leave one without the other.
- Pass keys downstream. If your handler calls a payment provider, derive its idempotency key from yours (or from your payment ID), so your retries can't create a second charge there.
- Generate the key once per user action, on the client, before the first attempt — not on each retry.
The same idea applies to message processing: see idempotent consumers and delivery guarantees. The System Design roadmap's payment lesson builds a complete idempotency layer.