What Is Idempotency? How to Stop Duplicate Payments and Orders
Press the pay button twice — does the customer get charged twice? Idempotency is the concept that prevents this common failure. Here is what it means, how HTTP methods differ, and how an idempotency key stops duplicate payments and orders.

Press Pay Twice — Does the Customer Pay Twice?
When an online payment stalls for a second, almost everyone presses the button again. So does the customer actually get charged twice? In a well-built system, pressing twice still charges once. The concept that guarantees this is idempotency.
It sounds like developer-only jargon, but it is a problem product managers and operations teams hit even more often. "Refund a duplicate charge," "two orders came in for one purchase," "points were credited twice" — these incidents all live in the gap where idempotency is missing. This post covers what idempotency is, why it matters most for payments and orders, and what you actually need to decide in practice.
What Idempotency Really Means — Same Result, However Many Times
Idempotency, in one line, is the property that sending the same request once or many times produces the same result as sending it once. The word comes from math, but the idea is intuitive. A light switch makes it clear.
- "Turn the light off" is idempotent. If it is already off, turning it off again still leaves it "off." Ten presses, one result.
- "Toggle the light" is not idempotent. Each press flips the state, so the result changes every time.
Applied to payments: "set this order to paid" can be designed to be idempotent, but "add one new charge" is not idempotent if left as-is. Send it twice and you get two charges.
Idempotent vs Non-Idempotent Operations
The key is the difference between setting state to a specific value and adding something new. Operations that set a value (replace, delete) are naturally idempotent; operations that add (create, accumulate) are not, unless you make them so. That is why the dangerous side is always the "add new" actions — payments, orders, point credits.
Idempotency Through HTTP Methods
Web APIs already draw this line at the method level. Here is what the standard (RFC 9110) defines as idempotent.
| Method | Idempotent | Meaning |
|---|---|---|
| GET | Yes | Read — the data does not change no matter how often you read |
| PUT | Yes | Full replace — overwriting with the same value gives the same result |
| DELETE | Yes | Delete — deleting twice still leaves the "gone" state |
| POST | No | Create — sending twice creates two records (duplicate charge!) |
| PATCH | No | Partial update — accumulating changes shift the result |
The catch is that "create" actions like payments and orders are almost always POST, and POST is not idempotent by standard. If the network drops and the client retries, or the user presses twice, you simply get two records. That is why a separate safeguard is needed.
How Payments and Orders (POST) Are Protected — The Idempotency Key
The standard way to make a non-idempotent POST idempotent is the idempotency key (Idempotency-Key). Payment APIs like Stripe and Adyen all offer it, and the IETF is standardizing it as an HTTP header — it is a well-settled pattern.
How It Works — One Key Absorbs Duplicates
The mechanism is three simple steps.
- The client sends a unique key (usually a UUID) along with the request.
- The server stores the result under that key. If the key is new, it processes normally.
- If the same key arrives again, it does not reprocess — it returns the original stored result.
A request looks roughly like this.
[code lang="text"] POST /orders Idempotency-Key: 8f3a1c2e-4b6d-49a1-9b7d-2e5c1a0f7d33
First request → create the order, store the result with the key Same key again → no new order; return "already processed + same result" [/code]
Now whether the user presses twice or the client auto-retries after a timeout, the result is always one record. Because it makes retries safe, idempotency pairs with fallback design as one of the two pillars of system reliability.
What Product Managers Need to Decide
Developers implement idempotency, but deciding what should be idempotent is often a product and operations call. These four are worth settling at the spec stage.
| Decision | Question to answer |
|---|---|
| Who generates the key | Does the client create the UUID, or does the server issue it? |
| Retention window | For how long is the same key treated as "the same request"? (Stripe uses 24 hours) |
| Response on duplicate | Return the original stored result, or signal an error? |
| Scope | Payments and orders only, or also point credits and notifications? |
That last row is the one most often missed. Teams guard the payment but let "credit points" and "push notification" fire twice. The full set of side effects from a single user action should fall under the idempotency scope.
When You Need to Care About Idempotency
Not every request needs an idempotency key. Reach for it when:
- The action leaks money, inventory, or trust if it runs again (payment, order, refund, transfer, point credit)
- The client auto-retries the call (a re-request after a network timeout)
- The flow integrates with external systems where a duplicate becomes an incident immediately
Conversely, a plain read (GET) or a state change designed to be idempotent (PUT/DELETE) is already safe and needs no extra machinery. Deciding "what to guarantee" centrally, up front, is the same mindset behind a closed-loop operation.
A System That Is Safe to Press Twice
Idempotency is not a grand theory — it is a very practical safeguard that keeps "the user pressed twice" from becoming an incident. The core is simple: the same request, however many times it arrives, has the same effect as once. For add-type actions like create, pay, and credit, attach an idempotency key to absorb retries and double-clicks.
If you are a product manager, write "what happens if this action runs twice?" into the spec. If you are an engineer, make the idempotency key a default on every payment and order endpoint. A system that is safe to press twice is, in the end, the foundation of trust that lets users confidently press pay.