Skip to main content

Idempotency & Request Retries

How to implement idempotency keys, Redis-based locking, and safe client retry logic.

Topic:Reliability & OperationsUpdated:2026-10-14
0000
store empty · charges 0
A request card carries its Idempotency-Key in the holes along its edge and its body in the grid. A new key raises its tab while the press makes the charge, then the response is stored on that key's spike. A retry with the same body gets the stored response back without a second charge; a changed body is rejected with 422, and a duplicate sent while the tab is still up is rejected with 409.

Introduction to Idempotency

An API operation is idempotent if making the same request multiple times yields the same result and server state. While HTTP GET, PUT, and DELETE are natively idempotent, POST (typically used for billing, messaging, or resource creation) is not.

To prevent duplicate actions due to network interruptions, APIs must support idempotency keys.


1. Idempotency Keys Workflow

Clients send a unique identifier (usually a UUID) in the Idempotency-Key request header.

Server Execution Flow:

  1. Receive Request: Check if the Idempotency-Key exists in cache (e.g., Redis).
  2. First Time Seeing Key: Save a lock record processing in Redis, together with a fingerprint (a hash) of the request payload, and execute the database transaction.
  3. Locking Concurrent Calls: If a second request arrives with the same key while the first is running, return a 409 Conflict with a short Retry-After.
  4. Cache the Result: Once the transaction completes, save the HTTP status code and response body in Redis alongside the key, setting a TTL (e.g. 24 hours).
  5. Replays: If the client calls again with the same key after completion, bypass the database, load the cached response from Redis, and return it instantly.
  6. Reject a Reused Key: Before replaying, compare the new payload’s fingerprint with the stored one. If they differ, the client has reused a key for a different request: return 422 Unprocessable Content with an idempotency key conflict error, and never replay the stored response or execute the new request.

These status codes follow the IETF Internet-Draft draft-ietf-httpapi-idempotency-key-header (revision -07, now expired and not an RFC), which also recommends 400 Bad Request when an operation documented as requiring the header receives a request without it.


2. Request Retries & Jitter

When API clients experience network timeouts or receive 429 or 503 codes, they will attempt to retry the request. To prevent hammering the server, client libraries should implement Exponential Backoff with Jitter.

Algorithm:

  • Exponential Backoff: Multiply the wait time exponentially for each failure (e.g. 1s, 2s, 4s, 8s).
  • Jitter (Randomness): Add random variation to the wait time. Without jitter, all clients retrying a downed service will backoff and strike the service at the exact same synchronized intervals, creating a “thundering herd” effect.
sleep = random_between(0, base * (factor ^ attempt))