flashman
← All guides

HTTP Idempotency-Key retries that stay safe

Design Idempotency-Key retries with scoped keys, request fingerprints, stored outcomes, and replay rules so a lost response cannot repeat the side effect.

2026-09-29 · 8 min read

  • http
  • api
  • retries

Clients retry when a timeout leaves them unsure whether the server committed. An idempotency key gives both sides a stable name for that attempt so the second delivery can return the first result instead of repeating the action.

The header does not make an arbitrary POST safe. Safety comes from storing the request fingerprint and the outcome before the client is told the operation finished, and from refusing to reuse a key for a different payload.

Scope every key to one operation

Bind the key to the authenticated caller, the target account or tenant, and the route. A key that is unique only on the client can collide across users. A key reused for a different operation should not return another operation's stored response.

  • Mint one key per user action, then reuse it on retries.
  • Store the key before performing the external side effect.
  • Fingerprint the body and the headers that change meaning.
  • Document how long stored responses remain replayable.

Persist outcome and uncertainty

Record an in-progress marker first so a concurrent duplicate does not start a second transaction. After the effect commits, store the status code and response body that retries should see. If the process crashes in between, recovery must find the effect and finish the record rather than starting over.

Include a fingerprint of the canonical request. An exact match replays the stored response. A different payload for the same key is a conflict the client must fix with a new key.

Align expiry with client retries

Keep the record at least as long as the client's maximum retry schedule, including background jobs that resume after a deploy. Once the record expires, a late retry is a new operation and needs a new key if repeating the effect would be harmful.

Tell clients which header to send, which error represents a payload conflict, and which response means the original request is still running. Ambiguous 500 errors are why they retry in the first place.

Do not confuse this with conditional writes

ETags and If-Match prevent lost updates between two different edits. An idempotency key prevents one edit from being applied twice. APIs that move money, send messages, or create billable objects often need both: a concurrency check for the resource and a retry key for the request.

GET and PUT semantics do not replace a key on a POST that creates a new charge. Choose the mechanism that matches the side effect.

Exercise lost responses on purpose

Use Flashman's UUID generator for synthetic keys, the timestamp converter for retention windows, the JSON formatter for stored responses, diff for original and retry bodies, and the hash tool for request fingerprints.

Test a dropped success response, identical retries, changed payloads, concurrent duplicates, crashes after the side effect, expired keys, cross-tenant key reuse, and a retry budget longer than the retention window.

Try these tools