Node.js & APIs · 20 min · 160 XP

Retries and idempotency keys

Make a retried request safe to repeat with an idempotency key, so a flaky network never charges anyone twice.

A client sends POST /payments. The server charges the card, and then the connection drops before the response arrives. From the client's side, the request failed. Should it retry? If it does, and the server simply runs the request again, the customer pays twice. If it doesn't, and the first attempt really did fail, the order is never paid. The client cannot tell these apart. Timeouts, mobile networks and impatient double-clicks make this an everyday event, not an edge case.

An operation is idempotent when doing it twice has the same effect as doing it once. GET, PUT and DELETE are defined that way: setting a value to 5 twice leaves it at 5. POST usually isn't: creating twice creates two. The standard fix is an idempotency key: the client generates a unique value (a UUID) for each intended operation and sends it in a header, reusing the same key on every retry of that operation.

What the server does with the key
Idempotency-Key: 7f3c…   (same key on every retry)

new key                  → record "in progress", do the work, store the response
seen, finished, same body → return the stored response; do nothing again
seen, still in progress   → 409: the first attempt hasn't finished yet
seen, different body      → 422: this key already means something else
the work failed           → forget the key, so a retry can try again

Two of those rows are where implementations go wrong. In progress: two copies of a request can arrive at the same moment, for example a double-click. If the server only records the key after the work finishes, both copies see an unknown key and both charge. Record the key before starting, and atomically: in a database, that's a unique constraint on the key column, so the second insert fails rather than racing. Different body: a buggy client that reuses one key for two different payments must get an error, not the first payment's receipt.

Payment APIs such as Stripe's accept an Idempotency-Key header for exactly this reason, and keep keys for a limited time (about a day) rather than forever. When you call one, generate the key once per operation and reuse it on retries. A new key per attempt defeats the whole mechanism.

Loading your workspace…