Engineering

Idempotency: The Habit That Prevents Double Charges

Networks retry, users double-click, queues redeliver. The single most valuable defensive pattern for any endpoint that moves money or creates records.

Purushottam Kumar Suman
Purushottam Kumar Suman
Founder & CEO, Drema AI
7 min read
Digital market analysis display showing price trends

The most expensive defect we find when reviewing payment systems is also the easiest to prevent. A request times out, the client retries, and the customer is charged twice — even though the first request succeeded, silently, after the client gave up waiting.

01

A timeout is not a failure

When a request times out, the client knows only that it did not receive a response. The operation may have completed perfectly. This ambiguity is unavoidable in distributed systems, which means the retry is correct behaviour — and the server must therefore be able to recognise it.

A timeout tells you nothing about whether the work happened. Design as if it did.

02

How the key works

The client generates a unique key per logical operation and sends it with the request. The server stores the key with the result. If the same key arrives again, it returns the stored result rather than performing the work a second time. The client must reuse the same key on retry — generating a fresh one defeats the entire mechanism.

03

Store the response, not just the key

Recording that a key was seen is insufficient; the retry needs the same answer the original produced, including the payment identifier. Store the full response body and status against the key, with a retention window comfortably longer than any client's retry policy — 24 hours is a common and sensible default.

Client generatesOne key per logical operation
Same key on retryNever regenerate — that defeats it
Store the responseNot merely the fact of the key
Handle in-flightConcurrent duplicates need a lock, not a race
04

The concurrent case people miss

Two identical requests can arrive before the first completes — a double-click produces exactly this. Checking whether the key exists and then processing is a race condition. Insert the key first with a unique constraint so the second request fails to claim it and waits or returns a conflict, rather than both proceeding.

05

It applies well beyond payments

Anything that creates a record, sends a message, triggers a workflow or calls a partner API benefits. Queue consumers especially — most queues guarantee at-least-once delivery, which means redelivery is normal operation and the consumer must tolerate it.

06

Test the duplicate path

Write the test that fires the same request twice concurrently and asserts one effect. It takes ten minutes and it is the only way to know the protection works. Most idempotency bugs we find are in code that has an idempotency key parameter which nothing actually enforces.

24 hrs
Typical key retention window
Insert first
Then process — avoids the race
1 test
Concurrent duplicate, asserting one effect
Purushottam Kumar Suman
Written by
Purushottam Kumar Suman
Founder & CEO, Drema AI

Founder and CEO of Drema AI. Builds AI systems, SaaS platforms and industry software — and writes about what actually survives production.

CTA Background

Got a problem like this one?

Bring it to a call with a founder.You will get a straight answer, including when the answer is no.

View Our Work
AI-First Engineering
Secure & Scalable
Built to Deliver Impact