Idempotency lab
Retry a timed-out write and watch it double-charge — then add an idempotency key and watch it not.
POST /charges · { amount: $40.00 }No charges yet.
Empty — no keyed results saved.
- No requests yet — press Send, then Client retry, and watch the ledger.
Model: one intended $40.00 charge. Method semantics follow RFC 9110 §9.2 (safe and idempotent methods). The key store replays a saved status and body on repeat and rejects a reused key whose parameters changed — the behaviour described in the Idempotency-Key header Internet-Draft (draft-ietf-httpapi-idempotency-key-header, IETF HTTPAPI) and shipped by Stripe, which retains keys for at least 24 hours. Deterministic: no timers, no network, no randomness.
Why a retry is dangerous
A client whose request times out cannot distinguish a lost request from a lost response. The write may have happened. A fault-tolerant client retries anyway, because not retrying loses real work — and for a non-idempotent method that retry is a second side effect.
HTTP already answers half of this. RFC 9110 §9.2.2 defines GET, HEAD, PUT, DELETE, OPTIONS and TRACE as idempotent: repeating them has the same intended effect on server state as issuing them once. POST is not idempotent, and creating a charge is the canonical POST.
What the key actually does
An idempotency key moves the guarantee from the method to the request. The client generates a unique key, the server stores the outcome against it on first execution, and any later request carrying the same key replays the saved status and body instead of executing again. The Idempotency-Key header field is an IETF Internet-Draft rather than a published RFC, but the pattern is implemented widely.
Two details decide whether it works. The store must be written in the same transaction as the effect, or a crash between them reopens the hole. And a reused key with different parameters must be rejected rather than replayed, since that indicates a client bug, not a retry.