Skip to content

PATTERN Cited by 1 source

Idempotency-Key header

Pattern

Support an Idempotency-Key (or equivalent) header on non-idempotent write endpoints. The caller supplies a unique key per logical operation; the server deduplicates by key and returns the cached original response on any replay. A retry after an ambiguous failure becomes safe: the server has seen the key before and either returns the stored result, or finishes the original in-flight operation and returns its result.

Zalando's timeouts post names the pattern as the enabling contract for safe retry on non-idempotent writes:

"For safely retrying requests without accidentally performing the same operation twice, consider supporting additional Idempotency-Key header in your API. When creating or updating an object, use an idempotency key. Then, if a connection error occurs, you can safely repeat the request without the risk of creating a second object or performing the update twice." (Source: sources/2023-07-25-zalando-all-you-need-to-know-about-timeouts)

Named public references: Stripe — Idempotent Requests and Amazon Builders' Library — Making retries safe with idempotent APIs.

Shape

On the client: - Generate a fresh key per logical operation (UUID v4 is typical). - Attach the key as Idempotency-Key: <uuid> on the write request. - On retry (network error, timeout, 5xx), reuse the same key. - Reuse keys across retries, never across logical operations.

On the server: - Store (key, request_hash, response, status) on first receipt. - On subsequent requests with the same key: - If request body matches stored hash → return stored response. - If request body differs → reject with 409 Conflict (key reuse for a different operation). - Age out stored keys after a window (Stripe: 24 hours; AWS products vary). - Enforce some form of serialisation / locking so two concurrent retries of the same key don't both execute.

Why key + hash, not just key

Storing only (key, response) creates a subtle bug: a client that reuses a key for a different payload will receive the wrong response. Storing (key, request_hash, response) and rejecting mismatches with 409 Conflict turns key reuse into a loud failure rather than a silent correctness hole.

Why storage, not just "first-one-wins"

First-one-wins — dedupe by key only on the first request — leaves a race: two near-simultaneous clients with the same key can both be in flight. Correct implementations either:

  • Lock on key while the first request executes; second blocks until first commits, then returns the stored response.
  • Optimistic insert with a unique constraint on key; the loser polls for the stored response.

Retention window

Idempotency storage is unbounded if keys are kept forever. Production implementations cap retention:

  • Stripe: 24 hours.
  • Many AWS services: 10 minutes to 24 hours.

Callers must complete retries within the retention window, or the key will be treated as fresh and the operation may re-execute. The retention bound, caller's retry bound, and service SLA must be consistent.

When to apply

  • Always: payment operations, order creation, any write with user-visible side effect.
  • Usually: account mutations, subscription changes, resource creation.
  • Not strictly needed: idempotent writes by construction (PUT full replacement, set-valued writes), reads.

Trade-offs

  • Server complexity: key storage + lookup + deduplication
  • aging logic. Typically implemented once as a shared service framework module.
  • Client discipline: client must remember to reuse keys across retries and not across operations. Easy to get wrong; lint rules and client-library wrappers help.
  • Latency on first request: slight overhead for the key lookup on every write.

Seen in

Last updated · 550 distilled / 1,221 read