jsonapirestbackend

Idempotency in JSON APIs: Safe Retries Explained

·8 min read·APIs

The duplicate-request problem

A client sends POST /payments, the payment succeeds, but the response is lost to a network timeout. The client, seeing no response, retries — and now you have charged the customer twice. This is one of the most common and costly API bugs. The fix is idempotency.

What idempotency means

An operation is idempotent if performing it multiple times has the same effect as performing it once. GET, PUT, and DELETE are naturally idempotent. POST is not — each call creates something new. To make POST safe to retry, you add an idempotency key.

How idempotency keys work

The client generates a unique key (a UUID) per logical operation and sends it in a header:

text
POST /payments
Idempotency-Key: 5b1f...c9
Content-Type: application/json

{ "amount": 4995, "currency": "INR" }

The server's logic:

text
1. Look up the idempotency key.
2. If seen before -> return the SAME stored response. Do not re-execute.
3. If new -> execute, store the response against the key, then return it.

Now a retry with the same key returns the original result without charging again.

json
{ "id": "pay_91", "status": "succeeded", "amount": 4995 }

Implementation details that matter

  • Store the response, not just the key. A retry must get the exact original response (same body, same status), so persist both.
  • Scope keys correctly. Tie a key to a specific endpoint and account so unrelated requests cannot collide.
  • Handle in-flight duplicates. If a second request arrives while the first is still processing, lock on the key and make the second wait or return a "request in progress" status.
  • Expire keys. Keep them for 24 hours or so, then purge — they are for retry windows, not forever.

Idempotency vs uniqueness constraints

A database unique constraint (one order per cart) prevents duplicates at the data layer, but the client still gets an ugly error on retry. Idempotency keys are better because the retry transparently returns the original success. Use both: the key for a smooth client experience, the constraint as a safety net.

Designing idempotent endpoints

  • Make PUT replace the whole resource (idempotent by design) instead of POST where you can.
  • For POST, require or support an Idempotency-Key header on anything with side effects (payments, emails, provisioning).
  • Document clearly which endpoints honor idempotency keys and how long they are retained.

Frequently asked questions

The client, once per logical operation — typically a UUID. The same key is reused only when retrying that exact operation.

Yes. GET, PUT, and DELETE are idempotent by definition. POST and PATCH generally are not, which is why they benefit from idempotency keys.

Long enough to cover realistic retry windows — 24 hours is common. Store the original response with the key so retries return identical results.

Try JSON Formatter

Format and inspect your API request and response bodies.