What is the Idempotency-Key HTTP header pattern used by payment and other write APIs, who generates the key, and which requests should carry one?
answer
- Client generates UUID per intent, reuses on every retry
- Header, not body — gateways and SDKs can see it
- First call executes and stores; repeats replay stored response
- Only for POST / non-idempotent PATCH
- Not needed for GET, PUT, DELETE
basics
~20 sThe client generates a unique key (usually a UUID) per logical operation and sends it as the Idempotency-Key request header. The server records the key with the result; if the same key arrives again it returns the stored response instead of re-executing. It makes retrying a POST safe.
solid answer
~50 sPOST is not idempotent, so when a response is lost the client cannot tell whether the charge happened. The `Idempotency-Key` header fixes that at the application level. **The client** generates the key — one per logical operation, typically a UUIDv4 — and **reuses the same key on every retry** of that operation. A new logical operation gets a new key. Generating it client-side is essential: only the client knows that attempt 2 is a retry of attempt 1. **The server** looks the key up. First time: execute, store the outcome against the key, respond. Subsequent times: return the **stored response** without re-executing, so the caller sees the original 201 and the original resource id. Carry a key on state-creating, non-idempotent requests: `POST /payments`, `POST /orders`, `POST /transfers`. It is unnecessary on GET (safe) and on PUT/DELETE (already idempotent by method). Send it as a request header, not in the body, so gateways and middleware can act on it.
code
http · 22 linesPOST /v1/payments HTTP/1.1
Host: api.example.com
Idempotency-Key: 8f14e45f-ceea-467a-9f5a-3c3f2f0d1b77
Content-Type: application/json
{"amount":2000,"currency":"usd","source":"card_1"}
HTTP/1.1 201 Created
Location: /v1/payments/pay_9K2
--- response lost; client retries with the SAME key ---
POST /v1/payments HTTP/1.1
Host: api.example.com
Idempotency-Key: 8f14e45f-ceea-467a-9f5a-3c3f2f0d1b77
Content-Type: application/json
{"amount":2000,"currency":"usd","source":"card_1"}
HTTP/1.1 201 Created
Idempotent-Replayed: true
Location: /v1/payments/pay_9K2go deeper
Explain the ambiguity a lost POST response creates, that the client generates a UUID and reuses it on retries, and that the server replays the stored response.
Add where the pattern applies (POST and non-idempotent PATCH, not GET/PUT/DELETE), why it is a header, and that the server needs a durable dedupe store.
Frame it as an application-level contract layered over HTTP because the protocol offers nothing here, and flag the hard parts you'd address next: scope, TTL, concurrent retries, payload mismatch.
Discuss it as a platform contract — SDKs that attach keys automatically, uniform semantics across services, storage and retention cost, and the boundary between this and optimistic concurrency control.
## The problem it solves HTTP gives PUT and DELETE idempotency, so a client library may safely re-send them when a response is lost. POST has no such guarantee, and POST is what you use for "charge this card" or "place this order". When the connection drops with no response, the client faces an ambiguity it cannot resolve: the request may never have arrived, or it may have been fully processed with only the response lost. Retrying risks a double charge; not retrying risks a payment the customer thinks failed but which actually went through. HTTP itself offers no mechanism to resolve this. The `Idempotency-Key` header is the industry's application-level answer — popularised by Stripe and other payment APIs, and now the subject of an IETF draft (`Idempotency-Key` header field). It gives the server a way to recognize "this is the same logical operation I already handled". ## How it works, end to end 1. The client decides to perform an operation (charge $20). Before sending anything, it **generates a unique key** — a UUIDv4 is the standard choice — and persists it alongside its intent if the operation matters enough to survive a client restart. 2. It sends `POST /payments` with `Idempotency-Key: 8f14e45f-…` and the request body. 3. The server looks up the key within its scope. Not found → it executes the operation, stores the key together with the resulting status, headers and body, and responds normally (say 201 Created). 4. The response is lost. The client retries **the identical request with the identical key**. 5. The server finds the key, does **not** execute again, and returns the stored response: same 201, same payment id. The client ends up with the original outcome. Exactly one payment exists. ## Who generates the key, and why it must be the client Only the client knows that its second attempt is a retry of the first, rather than a genuinely new operation. If the server generated keys, every request would get a fresh one and nothing would ever deduplicate. So the key is client-supplied and its lifetime is tied to the client's *intent*, not to the request. This has a practical consequence people get wrong: the key must be generated **before** the first attempt and reused across all retries of that intent, including retries after a process restart. If your retry loop generates a new UUID each pass, you have built an elaborate way of doing nothing. Conversely, a client that reuses one key for two genuinely different operations will silently get the first operation's response back for the second — so keys must never be derived from something non-unique like a user id or a timestamp with second resolution. A good key is: unique per operation, opaque, unguessable enough that another tenant can't collide with it, and long-lived enough to survive the client's retry window. UUIDv4 satisfies all of that. Some clients derive it deterministically from the operation's own natural identity (order id + attempt intent), which is fine as long as the derivation is genuinely unique per intended effect. ## Where it belongs and where it doesn't **Use it on non-idempotent state-changing requests**: `POST /payments`, `POST /orders`, `POST /transfers`, and non-idempotent `PATCH` operations such as balance adjustments. **Don't bother on**: GET and HEAD (safe — nothing to deduplicate); PUT and DELETE (already idempotent by method, since re-sending the same replacement or removal converges). Adding keys everywhere just adds storage and confusion. **Send it as a header, not a body field.** A header is visible to gateways, proxies, logging and routing layers, it is method- and content-type-agnostic, and it keeps deduplication independent of your payload schema. It also means an SDK can attach it automatically to every mutating request without understanding the body. ## What the pattern does not do It is not distributed transactions, and it is not a general concurrency-control mechanism — it doesn't stop two *different* clients writing conflicting updates (that is what ETag preconditions are for). It only collapses repeats of the *same* logical operation. It is also not free: the server must maintain a durable dedupe store, decide the key's scope, decide how long keys live, handle two retries arriving concurrently, and decide what to do when the same key arrives with a different payload. Those are the follow-up questions that separate a candidate who has read about the header from one who has implemented it. ## The one-line version The client names the operation with a unique key; the server promises that a key is executed at most once and that later arrivals get the original answer back. That turns an unsafe POST retry into a safe one.
- Should a client generate a new key on each retry attempt?No — that defeats the entire mechanism. The key identifies the logical operation, not the HTTP attempt, so all retries of one intent must carry the identical key. A new key is generated only when the client genuinely wants a second, separate operation.
- Do you need an idempotency key on PUT and DELETE?Normally not, because those methods are already idempotent by definition — re-sending the same replacement or removal converges on the same state. A key can still help if you want to replay the exact original response body or if the handler has non-convergent side effects, but the common case does not need it.
- Why is the key sent as a header rather than a field in the JSON body?A header is independent of the payload's content type and schema, so a gateway, proxy or SDK can read and act on it without parsing the body. It also lets a client library attach the key automatically to every mutating request, and keeps deduplication metadata out of your domain model.
Like a ticket number at a counter: you keep the same ticket while you wait, and showing it again gets you the same order, not a second one.
saying these in an interview costs you the question
- Saying the server generates the idempotency key.
- Generating a fresh key on each retry attempt.
- Putting the key in the request body instead of a header.
- Requiring keys on GET requests, which are already safe.
- Believing the header alone provides idempotency with no server-side dedupe store.