Idempotency: making retries safe

If a payment request times out, did it go through? Idempotency makes retrying safe. Learn idempotent methods and how idempotency keys work.

A single stamp pressed twice with one result

An operation is idempotent if doing it many times has the same result as doing it once. Setting a value to 5 is idempotent. Adding 5 to a value is not.

Why it matters

Networks fail. A client sends "charge 100 taka", the connection drops, and it never learns whether the server processed it. If it retries and the operation is not idempotent, the customer may be charged twice. Message queues that deliver at-least-once cause the same issue.

HTTP methods

GET, PUT and DELETE are meant to be idempotent. POST is not by default, and that is where you need extra care.

Idempotency keys

The client generates a unique key (for example a UUID) for each logical operation and sends it in a header such as Idempotency-Key. The server:

  1. Looks up the key.
  2. If it is new, processes the request and stores the key together with the response.
  3. If it already exists, returns the stored response without doing the work again.

Stripe's API popularised this pattern.

Implementation tips

  • Store the key with a unique constraint in the database, so two simultaneous retries cannot both win.
  • Save the key and the business change in one transaction.
  • Mark keys as "in progress" so a parallel duplicate waits or gets a conflict response.
  • Keep keys for a limited time (for example 24 hours).
  • If the same key arrives with a different body, reject it.

Natural idempotency

Where possible, design operations so they are idempotent anyway: "set status to shipped" instead of "advance status", or use a unique order ID so inserting twice fails the second time.

Takeaway: assume every request can arrive twice, and give every state-changing call an idempotency key.


Part 14 of 15 in the System Design series. Previous: API gateway: the front door of a microservice system.

Keep reading