I
Glossary
Idempotency Key
An idempotency key is a client-supplied identifier that a server records against the outcome of a write request. When the same key returns, the server skips the work and hands back the stored status code and body from the original attempt, so a retry after a timeout can't create a second charge.
Key Takeaways
The server stores an outcome, not a promise. Stripe saves the status code and body of the first attempt and replays that pair, 500 errors included.
Answering a replay with a fresh 200 breaks the contract: it hides whether the original attempt succeeded.
Two requests carrying one key can arrive before either finishes. The IETF draft says answer 409; Adyen returns 409 with error code 704.
Retries, keys, and exactly-once sit at three layers. A retry only resends, a key bounds one endpoint, and neither reaches a pipeline.
Retention is short enough to fall off. Stripe prunes keys at 24 hours; Adyen holds them a minimum of 7 days.
What does the server store against the key?
The server stores the finished outcome of the first attempt under the client's string. Stripe saves the status code and body of that first request whether it succeeded or failed, and later requests carrying the key get that same result back, 500 errors included.
A workable key record holds four things:
Stored field | What it holds | What the replay needs it for |
Key | the client's string, scoped per account and endpoint | finding the record |
Status code | 201, 402, or 500, whatever came out first | telling the caller what happened |
Response body | the serialized resource, id included | returning the same object id |
Request fingerprint | a checksum or field match over the payload | catching a changed payload |
Answering a replay with a fresh 200 OK looks harmless and isn't. It buries a stored 402 decline, and a fresh body carrying a new resource id means the work ran twice.
What happens when two requests with the same key arrive at once?
The second one loses, and it should get a conflict rather than a replay, because no outcome exists yet. It's the race a naive implementation misses, having only tested retries that land after the first call.
Published behavior across four sources:
IETF draft: a request retried before the original completed SHOULD get HTTP 409, a conflict.
Adyen: a duplicate sent while the first is in flight returns 422 or 409 with error code 704, request already processed or in progress.
PayPal: two simultaneous requests with one PayPal-Request-Id mean PayPal processes the first and may fail the second.
Stripe: a request conflicting with another executing concurrently never gets its result saved, so the caller can retry.
Writing the key row before the work starts, under a unique constraint, makes the race resolvable: the second insert fails immediately, and that failure is the conflict signal.
Treat a 409 as retryable and a 422 mismatch as terminal. Minting a new key on a conflict is how a duplicate charge gets created, the same mistake a badly placed payment retry makes.
How do idempotency keys, request retries, and exactly-once delivery differ?
They solve three separate problems that readers routinely collapse into one. A retry is a client behavior, a key is a server-side guard on a single endpoint, and exactly-once is a property no messaging system delivers end to end.
Mechanism | Who owns it | What it guarantees | Where it stops |
Request retry | the client or its SDK | the request goes out again | nothing about repeated effects |
Idempotency key | the server | one effect per key inside the window | one endpoint, HTTP only |
Exactly-once delivery | the messaging layer | at-least-once plus an idempotent consumer | a producer can still emit twice |
A key on the write endpoint does nothing for duplicates entering behind it, from a replayed queue. Those belong to event deduplication, which checks identity over the stream instead.
What breaks when a key comes back with a different payload?
The server rejects it, because it can't know which payload the caller meant. The IETF draft says reply with 422, and Stripe compares incoming parameters against the original and errors when they disagree.
Detection runs on a fingerprint, which the draft defines as a value the resource generates from payload data: a checksum, field value matching, or a request digest.
Mismatches usually trace back to one of these:
The key came from something too coarse, so an order id gets reused for a later amendment.
A client library injects a timestamp into the body, moving the fingerprint while the key stays put.
A field normalizes differently between attempts, so an identical payload still produces a different checksum.
The key expired first. Stripe generates a new request when a pruned key returns, so a reuse past the window creates a second object, no error.
That last one worries me most, because nothing surfaces. Log both fingerprints on rejection, since a usage event refused this way looks like a network failure.
Related terms
A safe retry only covers the request layer, so these five pick up where the key stops working:
Event deduplication catches repeats inside a pipeline, past the reach of an HTTP header.
Payment retry is the scheduled reattempt of a declined charge, and the place a stale key does real damage.
Event ingestion is the endpoint that accepts usage records and enforces uniqueness on them.
Usage event carries its own identifier, which does this job inside a batch payload.
Usage reconciliation is the after-the-fact check for duplicates and gaps no key stopped.
FAQ
Where should a client create the idempotency key?
At the point the operation begins, above the retry loop. When the retry wrapper mints the value, every attempt carries its own key and every attempt executes, so the mechanism does nothing.
Do idempotency keys work across more than one endpoint?
No. A key names one operation on one endpoint for one account, so the same string sent to a refund endpoint and a charge endpoint means two unrelated records.
What should a client do after a 409 conflict on an idempotent request?
Wait, then resend the identical request with the same key. A 409 means the original is still executing, not that it failed. Generating a fresh key there turns a conflict into a real duplicate.
Is Idempotency-Key a standard HTTP header?
Not yet. It sits in an IETF HTTPAPI working group draft rather than a published RFC, which is why implementations diverge: Stripe and Adyen read Idempotency-Key, PayPal reads PayPal-Request-Id.
Back to glossary
















