Watch the video, transcript and sources
You think a timeout means your payment failed. The server can finish while the reply disappears, leaving your checkout confidently displaying absolutely nothing. Why can a retry charge again? How does Stripe remember an attempt? When should you stop retrying?
+ Same logical action, same key and parameters
+ API v1 replays the saved status and body
- A cached 500 can outlive the network problem
- Unresolved outcomes need a bounded retry windowThe payment finishes while the reply disappears
Picture buying a coffee. Stripe completes your payment request, and the response gets lost returning home. The customer sees a spinner. Their bank may have a more exciting interpretation. This is a conceptual checkout example; a timeout can leave the app uncertain about an operation the server already completed.
Idempotency means repeating an operation has the same intended effect as doing it once. An idempotency key labels one logical action. Generate the unique key before the first protected attempt, keep it for retries and persist it with the operation across application restarts. An earlier unprotected call cannot gain protection retroactively.
One saved result, another trip home
For Stripe API v1, the first request's status and body are saved after endpoint execution begins. Here is the exact wording from the current Stripe Docs:
For API v1, Stripe saves the status code and body of the first request made with a given idempotency key after endpoint execution begins. Retries with the same key return the saved response, including 500 errors.
The coffee stays put while its receipt travels again. The app decides whether another tap resumes the pending purchase or starts a new one. A genuinely new coffee gets a new key. A fresh key for every network retry defeats the protection.
The key has a contract
Changed parameters with the same key trigger a mismatch. Validation failures and conflicts with another execution save no idempotent result for that attempt. These requests can be retried; a competing attempt's conflict leaves the first execution's outcome unresolved.
These are API v1 rules. API v2 behaves differently. Email, inventory and your database each need failure handling, so the header cannot promise exactly-once delivery across your whole system.
Stripe retains v1 keys for at least 24 hours and can prune them afterward. A pruned key can execute a new request. Keep unresolved network retries inside the first day; beyond that, stop and reconcile the original outcome. Use exponential backoff and jitter, and check the retry defaults for your actual SDK.
The error can stick
In a separate error example, a cached 500 keeps replaying after connectivity recovers. The original operation may have produced side effects. Resolve its outcome through relevant objects, the Dashboard and webhooks before creating another operation identity. A new key can repeat the action.
I'd ship stable keys with bounded retries and reconciliation, so customers get coffee without funding your distributed systems education.
Verdict: SHIP IT — Stable keys · bounded retries · reconcile
Sources
https://docs.stripe.com/api/idempotent_requests
https://stripe.com/blog/idempotency
https://docs.stripe.com/error-low-level
https://www.rfc-editor.org/rfc/rfc9110.html#section-9.2.2
And that's the diff for today. I'm Niko from Axrisi. Merge responsibly.
YouTube · Newsletter · thedailydiff.dev · forward this to the intern who deployed on Friday.

