← All postsHow-to

Idempotency: making a retry safe to send twice

Idempotency is what stops a retried request charging a card twice. How idempotency keys work, which operations need them, and what the server has to remember.

How-toI

Networks fail in the most inconvenient way available: the request arrives, the work happens, and the response is lost. The client has no way to tell that from a request that never arrived, so it retries — and without idempotency the customer is charged twice, the invoice is created twice, and somebody spends a morning reconciling it. Idempotency is the property that makes sending the same request twice indistinguishable from sending it once.

Which operations need idempotency

  • Anything that creates something: payments, orders, invoices, users. These are the expensive duplicates.
  • Anything that sends something irreversible — an email, a notification, a webhook delivery to a third party.
  • Not reads. A GET is naturally safe to repeat, which is why it is worth keeping reads as reads.
  • Updates that set a value are usually already idempotent; updates that increment one are not, and that distinction catches people out.

How an idempotency key works

The client generates a unique key per logical operation — not per attempt — and sends it with the request. The server records the key with the result. If the same key arrives again, the server returns the stored result instead of doing the work a second time. The important detail is where the key comes from: it must be generated before the first attempt and reused for every retry of that attempt, which means it belongs to the operation in the client's own model, not to the HTTP call.

Decide what happens when the same key arrives with a different body. Returning the original result silently hides a client bug; rejecting it with a clear error surfaces one. The second is almost always right, and it has to be documented either way.

What the server has to remember

  1. Store the key, the result and the status — including that a request is in flight, so two simultaneous retries do not both execute.
  2. Set a retention window and publish it; twenty-four hours is a common choice and longer is safer for financial operations.
  3. Scope keys per account, so one caller cannot collide with or observe another.
  4. Return the original status code as well as the body, because a client treating a replay as a fresh success may act twice.
  5. Make the record durable. An idempotency cache that is lost on restart provides the guarantee only while nothing goes wrong, which is precisely when it is needed.

The client side of the bargain

Generate the key when the user acts, not when the request is built, and persist it alongside whatever you are creating. If your retry logic generates a fresh key each attempt, you have implemented retries with extra steps and no protection. This is also why at-least-once delivery is the normal guarantee for webhooks: the sender cannot know you received it, so the receiver has to be able to handle the same event arriving twice.

The Ettex Api documents the authentication model and the endpoints in one place, and the same discipline applies to anything consuming them through an agent, where retries are invisible to you. If you are designing a receiver rather than a caller, the practical rule is to treat every incoming event as a possible duplicate and key on the event identifier.

Frequently asked

Is a UUID good enough for an idempotency key?

Yes, provided it is generated once per logical operation and reused across retries. The failure is almost never collision; it is regenerating the key on each attempt.

How long should keys be kept?

Long enough to cover realistic retry windows — often twenty-four hours, longer for financial operations. Publish the number so clients know when a replay stops being recognised.

Are PUT requests already idempotent?

By definition they should be, if they set a value rather than modify it relatively. An endpoint that increments on every call is not idempotent whatever verb it uses.

MI
Written by Maria I.

Part of the Ettex team — writing about product, engineering and the future of work.

More posts
Get the best of the Ettex blogProduct news, guides and tips — straight to your inbox, no spam.