Home / Blog / Preventing Double Charges: A Practical Guide to Idempotency
PaymentsJune 8, 2026·3 min read

Preventing Double Charges: A Practical Guide to Idempotency

A timed-out payment request gets retried and your customer is charged twice. Idempotency keys are the simple pattern that prevents it. Here's how.

Picture a customer tapping Pay on a flaky mobile connection. The request reaches your server, the charge goes through, but the confirmation never makes it back. The app shows a spinner, then an error. The customer taps Pay again. Did they just pay twice?

In a system without safeguards, the answer is often yes. This is one of the most common and damaging bugs in payment software, and the fix is a single well-understood pattern: idempotency.

The network forgets, but money doesn't

Networks fail in an asymmetric way. A request can succeed on the server while the response is lost in transit. From the client's point of view, a successful charge and a completely failed one look identical — both end in a timeout. The only safe reaction the client has is to retry.

That's the trap. Retries are necessary for reliability, but a naive retry on a payment endpoint creates duplicate charges, duplicate orders, and duplicate confirmation messages. You can't tell clients to stop retrying; you have to make retries safe.

What an idempotency key actually does

An idempotency key is a unique token the client generates once per logical operation and attaches to every attempt of that operation — the first try and all retries.

The server's contract is simple:

The customer's second tap carries the same key as the first, so the server recognizes it, returns the original receipt, and charges nothing extra. The retry becomes a no-op that still gives the client a correct answer.

A few rules make this reliable in practice:

Watch the race condition

The subtle failure mode is concurrency: two retries arrive at almost the same instant, both check the store, both see no existing key, and both proceed. Now you've charged twice anyway.

The fix is to make claiming a key atomic. Put a unique constraint on the key column and let the database reject the second insert, or use an atomic operation in your key-value store. The first writer wins and does the work; the loser waits and returns the stored result. Never split the check and the write into two separate steps.

It's bigger than payments

Once you internalize idempotency, you see the need everywhere money-adjacent systems touch the network: creating orders, decrementing inventory, sending a WhatsApp confirmation, processing an incoming webhook. Anywhere a duplicate would cause harm, the same key-and-store pattern applies.

Reliability isn't the absence of retries. It's making retries safe to repeat.

Build idempotency in from the first version of any payment flow. Bolting it on after the first double-charge complaint is far more expensive — and customers remember being charged twice.

Build with Abati Technology

We build software that ships — WhatsApp API, developer tools, POS, and mobile apps. Let's talk about your project.

Get in Touch →