Skip to content

Conditional Requests & Idempotency Keys

The same validators that speed up reads also make writes safer. Two patterns handle the hard cases: concurrent edits and retried creates.

When two clients edit the same resource, the second can unknowingly overwrite the first — the “lost update” problem. Conditional writes prevent it: the client sends the ETag it last saw in If-Match, and the server applies the change only if the resource still matches. If it has changed, the server returns 412 Precondition Failed:

sequenceDiagram
  participant A as Client A
  participant S as Server
  A->>S: PUT /articles/42 (If-Match: "v7")
  alt still "v7"
    S-->>A: 200 OK (now "v8")
  else changed to "v8"
    S-->>A: 412 Precondition Failed
  end
If-Match rejects a write built on a stale version

The client then re-fetches, reapplies its change on the current version, and retries — no silent overwrite.

Recall that POST is not idempotent, so a retried create can duplicate. An idempotency key fixes this: the client generates a unique key and sends it with the request; the server stores the result against that key and returns the same result for any repeat.

JavaScript

A common convention is an Idempotency-Key request header; the server keeps the mapping for a window (say 24 hours) so retries within that window are safe.

No optimistic locking on write operations Symptom:

  • User A and user B both open the edit form for the same record
  • User A saves first; user B’s save silently overwrites it
  • Use If-Match: <etag> — the server returns 412 if the ETag no longer matches

A client that generates a fresh idempotency key on every retry Symptom:

  • The retry logic generates a new UUID on every attempt
  • The server treats each attempt as a new request — the duplicate still gets created
  • Generate the idempotency key once per operation and reuse it on retries

💡 Real-world examples

Stripe API:

  • An Idempotency-Key header on POST /charges, POST /payment_intents
  • Store the key on the client — retrying with the same key is safe

GitHub API:

  • An If-Match header for update operations that need optimistic locking
  • 412 Precondition Failed when the resource changed between the client’s read and its update

Sources: Stripe idempotent requests, GitHub REST API — best practices

What problem does an If-Match conditional write prevent?
When a precondition fails because the resource changed, which status?
How does an idempotency key make a retried POST safe?