✦ Insights

Idempotency Keys Are a Product Feature

A frozen screen and a second tap should never mean a double charge. Why idempotency keys belong in your product design, your documentation and your support tooling, not just your headers.

EngineeringAltisly Engineering2 April 20265 min read
All posts

The double charge nobody meant to cause

A customer taps pay. The screen freezes. The mobile network drops for a second, so they tap again. Somewhere behind the scenes, two requests arrive. If your system treats them as two separate payments, you have just taken money twice and created a support ticket, a refund and a person who is slightly less likely to trust you.

Most engineers know the cure: idempotency keys. Fewer treat them as something the customer, the integrator and the support team will actually experience. We think they deserve to be designed as a feature, not bolted on as a header.

What an idempotency key actually promises

An idempotent operation produces the same result however many times you perform it. An idempotency key is how a caller tells your system, "this request is the same intended action as one I sent before."

The contract is simple to state. The client generates a unique key for each distinct action and sends it with the request. The server records the key along with the outcome. If the same key arrives again, the server does not repeat the action. It returns the original outcome.

That promise is what makes retries safe. Without it, a client that gets a timeout faces an impossible choice: retry and risk a duplicate, or stop and risk a loss. With it, the answer is always to retry.

The details that decide whether it works

The idea is easy. The implementation is where it goes wrong.

Store the key before the work, not after. If you perform the action and then record the key, a crash in between leaves you with a completed action and no record. Record the key first, mark it in progress, do the work, then mark it complete, ideally in the same database transaction as the action itself.

Handle concurrent duplicates. Two identical requests can arrive at the same moment. A unique constraint on the key means only one gets to proceed. The other should wait or receive a clear response saying the original is still being processed, not run a second time.

Bind the key to the request. If a client reuses a key with a different amount or recipient, that is a bug, or worse. Store a fingerprint of the original request and reject mismatches with a clear error. Silently returning the old result for a different request would be confusing, and silently executing the new one would defeat the purpose.

Return the original response. Replaying should return what the first call returned, including a failure if it failed in a final way. Callers should not be able to tell the difference between the first answer and a repeat, other than perhaps a header noting it was a replay.

Decide how long keys live. Keys cannot be kept forever, but they must outlast any realistic retry window. A day is a common starting point for payment APIs. Whatever you choose, document it, because a retry after expiry looks like a new request.

Why this belongs in the product

Here is the point where idempotency stops being an internal concern.

Integrators read your documentation to learn how to make your API safe. If keys are optional, hidden or vaguely described, they will skip them, and their customers will pay the price. If you require a key on every money moving endpoint, you push the right behaviour onto everyone by default.

Your own interface matters too. A mobile app that generates a key when a user opens a payment screen, and reuses it for every tap on that screen, gets double tap protection for free. A key generated at the moment of each tap provides none. That is a product decision as much as a technical one.

And support teams feel it. When a customer reports a duplicate, the first question is whether it was one action or two. If every request carries a key, and the key appears in your logs and your operator tools, the answer takes seconds rather than an afternoon.

Where keys come from

Who generates the key? Almost always the client, since only the client knows whether two requests represent the same intent. A random identifier is usually enough. For some workflows, deriving the key from the business event is better: an invoice number combined with a payment attempt, say. That way two different systems that both try to pay the same invoice collide instead of both succeeding.

Avoid keys that reuse across unrelated actions, and avoid anything predictable that another party could guess and abuse.

What keys do not do

Idempotency keys protect against duplicate submissions. They do not protect against a user who deliberately submits the same payment twice, and they should not. They also do not solve problems in downstream systems. If your provider does not honour keys, your retry may still create a duplicate on their side, so you need your own reconciliation to catch it.

A short list for your next API

Before you ship a money moving endpoint, check the following:

  • Is a key required rather than optional?
  • Is it recorded before the action and committed with it?
  • Are concurrent duplicates handled?
  • Does a mismatched request with the same key fail loudly?
  • Is the retention window documented?
  • Can support find the key from a customer complaint?

An idempotency key is a small promise to a customer that pressing the button twice will not cost them twice. It is cheap to keep, and expensive to break.

IdempotencyAPIsPayments
A

Altisly Engineering

Writing about systems architecture, operations software and what it takes to keep a build coherent, at Altisly.

Have a problem worth solving?

Tell us what is not working, what you are trying to build, or where you want to go next.

What happens next
01You describe the operation.
02We reply within one business day.
03If there is a fit, we tell you what the next step looks like.