What is an idempotency key, and why do payments need one?
Because a customer whose connection drops will press pay again, and your system has to know that is the same payment.
Answered in short
5 things that decide this
- 01An idempotency key is a unique value your system attaches to a payment request, so the processor can recognise a retry of that exact request rather than treating it as a new one.
- 02Stripe saves the status code and body of the first request against the key, and later requests with the same key return that same stored result, including a 500.
- 03Retention differs by vendor and is not standardised: Stripe documents a 24-hour window, while Adyen states its keys are valid for a minimum of seven days.
- 04Sending the same key with different parameters is an error at Stripe rather than a silent second charge, which is the behaviour you want.
- 05The IETF draft for an Idempotency-Key header expired in 2026 and never became an RFC, so vendor behaviour is convention rather than a specification.
Your customer did nothing wrong
A payment request leaves the phone. The network stalls. No response comes back. The customer sees a spinner, waits, and taps pay again.
Nobody involved made a mistake. The first request may have succeeded, may have failed, or may still be running. The client has no way to tell, and Stripe describes exactly this state: clients are left not knowing whether the server received the request.
The key is what turns an unanswerable question into a safe action. Retry with the same key, and the processor either does the work once or hands back what it already did.
How three processors actually behave
Checked 11 August 2026 against each vendor's own documentation. Note the wording: these are retention statements, not promises about your application logic.
| Processor | Header | Retention, in their words | Key length |
|---|---|---|---|
| Stripe | Idempotency-Key | Keys "expire out of the system after 24 hours", and a reused key after pruning generates a new request | Up to 255 characters |
| Adyen | Idempotency-Key | "Valid for a minimum period of 7 days after first submission" | Maximum 64 characters |
| PayPal | PayPal-Request-Id | No platform-wide window. PayPal tells you to check the reference for your specific API | 38 single-byte characters |
Four rules that prevent the double charge
Generate the key before you send, not after a failure. Stripe suggests a version 4 UUID, or another random string with enough entropy to avoid collisions. It warns against using an email address or any personal identifier as the key.
Keep the key attached to the attempt, not to the button. If your customer genuinely wants to buy a second time, that is a new attempt and it needs a new key. Reusing a key across two real purchases means the second one silently returns the first result.
Treat 500-level errors as unresolved. Stripe says to treat them as indeterminate, and that it tries to reconcile partial state without guaranteeing the outcome. Your reconciliation job, not your retry loop, is what settles those.
Protect your own writes too. The key stops the processor charging twice. It does nothing about your application creating two orders, sending two confirmation emails, or decrementing stock twice, and that half is entirely your build.
- A 429 is the exception: Stripe notes rate limiting runs before the idempotency layer, so the safest handling of 4xx errors is a fresh key.
- Generate the keyRandom, per attempt, before sending.
- Send with the keyAttached to the payment request.
- No answerTimeout or dropped connection.
- Retry, same keySame key and same parameters.
- Get one resultThe original outcome, replayed.
- ReconcileFor anything still indeterminate.
The fourth station is the one teams get wrong. Retrying with the same key but different parameters produces an error at Stripe, which is deliberate and much better than a surprise.
Systems where a payment could not be repeated
Related questions
01Do idempotency keys work on GET and DELETE requests?
Stripe says not to send them there, because it has no effect and those requests are idempotent by definition. Its idempotency layer applies to POST requests, which are the ones that create something new. Adyen states the same split for its API.
02What happens if two identical requests arrive at once?
Stripe does not save a result when a request conflicts with another running at the same time. Its HTTP reference lists 409 Conflict for a request that clashes on the same key. You can retry those. Building for a 409 rather than being surprised by one is the practical difference.
03How long should we keep our own record of a key?
At least as long as your processor honours it, and usually longer, because your reconciliation runs on your data rather than theirs. If your vendor documents 24 hours, a key you retry on day three is a fresh request to them and a duplicate to you. Your own store is what catches that.
04Is this only a payments problem?
No, and payments are simply where it costs the most visibly. Any request that creates something has the same exposure: sending a message, filing a claim, booking a slot. We build idempotent writes on those paths for the same reason, since a duplicate booking is cheaper than a duplicate charge but not free.
Related
- Idempotency key →The term itself, defined.
- Why you never use floats for money →The other bug that quietly breaks a ledger.
- Settlement vs authorisation →Why a charge and a payment are not the same event.
- Ecommerce development →Where we build this properly.
- What PCI scope actually means →Which questionnaire applies to your checkout.

