An idempotent operation is an action that produces the exact same result whether you apply it once or multiple times.
In software, an idempotent request can be safely retried without side effects like double charges or duplicate orders. Apps make this work with an idempotency key: a unique ID sent with the request so the server can spot repeats and return the original result instead of running the action again.
Table of contents
- What does idempotent mean?
- Why does idempotency matter?
- What is an idempotency key?
- How do idempotency keys work?
- Where is idempotency used?
- Where does idempotency live in your codebase?
- How Stripe uses idempotency keys
- Which HTTP methods are idempotent?
- How to prompt idempotency as a vibe coder
- Common mistakes to avoid
- Jargon decoder
- FAQ
- Key takeaways
What does idempotent mean?
The word sounds scary. The idea is simple. Richard Feynman's rule was: if you can't explain it to a kid, you don't really get it. So here's the kid version.
1. The elevator button.
You press the call button. The elevator is coming. You press it four more times out of impatience. Still one elevator. Pressing again changes nothing. That button is idempotent.
2. The light switch set to "off."
Flip a switch to off when the light is already off. Nothing happens. You can do it 50 times. The room stays dark. Idempotent.
3. The vending machine (the opposite).
Each time you press "buy," you lose another dollar and get another soda. Pressing twice means two sodas and two charges. That is not idempotent.
The one-line test: ask, "If this accidentally happens twice, is the world any different than if it happened once?" If the answer is no, it's idempotent.
The word itself comes from Latin: idem ("the same") + potent ("power"). Same power, no matter how many times you apply it.
Why does idempotency matter?
Because the internet drops messages, and people double-tap. Without idempotency, every retry is a chance to charge someone twice.
Picture a payment app:
- A user taps Send Money.
- Their phone sends the request. Your server processes the payment.
- The Wi-Fi cuts out before the "Success!" reply reaches the phone.
- The phone shows a spinner, then an error. The user panics and taps Send Money again.
Here's the trap: the phone has no way to know whether step 2 worked. From its side, "the request never arrived" and "the reply got lost" look exactly the same.
- Without idempotency: the server sees a brand-new request and sends the money a second time.
- With idempotency: the server says, "I already did this one. Here's the answer I gave you before." One payment, no harm done.
The same problem shows up when apps retry automatically after a timeout, when a user refreshes a checkout page, or when a mobile app reconnects after going through a tunnel.
What is an idempotency key?
An idempotency key is a unique ID your app attaches to a request so the server can recognize a repeat. It's usually a UUID, a long random string like f47ac10b-58cc-4372-a567-0e02b2c3d479.
Think of a coat-check ticket:
- You hand over your coat and get ticket #47.
- Later you come back with #47. The attendant doesn't take a new coat. They look up #47 and hand you the same coat.
- Come back with #47 three times, and you still get one coat, not three.
The idempotency key is ticket #47. The server is the attendant. The saved result is your coat.
Two rules make the ticket work:
- One key per real action. A new checkout attempt gets a new key. A retry of the same attempt reuses the old one.
- The key is made before sending. The app creates it first, so every retry carries the exact same ticket.
How do idempotency keys work? Step by step
The server checks every key against the keys it has already handled. New key: do the work. Known key: replay the saved answer.
- The user taps Pay. The app generates a UUID and ties it to this one checkout attempt.
- The app sends the request with the key in a header, usually named
Idempotency-Key. - The server asks the database: "Have I seen this key before?"
- New key: the server charges the card, then saves the key together with the result.
- Known key: the server skips the charge and returns the saved result.
- The reply gets lost? The app retries with the same key, and step 5 kicks in. The user is charged once.
Where is idempotency used?
Anywhere money moves or a duplicate would cause a mess. Stripe, PayPal and banks all rely on it.
- Payments — charging a card once, not twice
- Money transfers — sending $500, not $1,000
- Refunds — refunding an order once
- E-commerce orders — one checkout, one order, one shipment
- Bookings — one seat, one hotel room, one appointment
- Sign-ups and invites — one account, one welcome email
- Webhooks — handling a "payment succeeded" event once, even when the sender delivers it twice
- Background jobs — a task that gets retried after a crash shouldn't send 3 invoices
A quick gut check: if a duplicate would make a customer email support, that action needs idempotency.
Where does idempotency live in your codebase?
Mostly in the backend, with a small but important job for the frontend and the database. Idempotency isn't a library or a language feature — it's a design rule you can build in JavaScript, Python, Go or anything else.
| Layer | Its job | What it looks like |
|---|---|---|
| Frontend (app or website) | Create the key and stop double-clicks | Generate a UUID when the user hits Pay; disable the button while the request is pending; reuse the same key on retry |
| Backend (API) | Check the key before doing the work | Look up the key; if found, return the saved response; if new, do the work and save the response with the key |
| Database | Make duplicates impossible | A unique constraint on the key column, so two copies of the same key can never be stored, even if two requests arrive at the same instant |
Why the database part matters: two retries can hit your server at the exact same millisecond. Both might check "seen this key?" before either has saved it. A unique constraint is the bouncer at the door: only one gets in, the other is turned away.
How Stripe uses idempotency keys (real-world example)
Stripe is the textbook example: send an Idempotency-Key header, and a retried charge never charges twice.
-
The setup. When your app asks Stripe to charge a card, it adds a header called
Idempotency-Keywith a unique value made for that one checkout attempt. - The happy path. Stripe runs the charge and saves the status code and response body next to the key, whether the request succeeded or failed.
- The retry. The connection drops before your app hears back. Your app doesn't know if the charge went through, so it sends the same request with the same key.
- Stripe's move. It recognizes the key, skips charging the card, and returns the saved response. One charge, and your app finally gets its answer.
- The safety check. Reuse a key with different details (say, a different amount) and Stripe returns an error. One ticket can't be used for two different coats.
- The expiry. Stripe keeps keys for at least 24 hours and may delete them after that. A key reused after it's deleted is treated as a brand-new request.
Good to know:
- Keys can be up to 255 characters. Don't use emails or personal info as keys.
- Every
POSTrequest accepts a key.GETandDELETEdon't need one, because they're already idempotent by design. - Stripe's official SDKs can generate keys and retry for you.
curl https://api.stripe.com/v1/customers \
-u sk_test_your_key: \
-H "Idempotency-Key: KG5LxwFBepaKHyUD" \
-d description="My first test customer"
Source: Stripe API docs — Idempotent requests, and Advanced error handling.
Which HTTP methods are idempotent?
GET, PUT and DELETE are idempotent by design. POST is not, which is exactly why POST requests need idempotency keys. An HTTP method is just the verb your app uses when it talks to a server.
| Method | Plain-English job | Idempotent? | Why |
|---|---|---|---|
GET |
Read something | ✅ Yes | Reading a page 10 times doesn't change it |
PUT |
Replace something with this exact version | ✅ Yes | Setting your name to "Ana" twice still leaves it as "Ana" |
DELETE |
Remove something | ✅ Yes | Deleting an already-deleted item leaves it deleted |
POST |
Create something new | ❌ No | Each call can create another order, charge or post |
PATCH |
Change part of something | ⚠️ Not always | "Set price to $10" is safe; "add $10 to price" is not |
The rule of thumb: "set" is safe to repeat, "add" is not. Setting a value lands in the same place every time. Adding stacks up.
How to prompt idempotency as a vibe coder
You don't need to write the code. You need to ask for it by name, because AI coding tools rarely add idempotency unless you do. Copy these into Claude Code, Cursor or any AI coder.
1. The all-in-one prompt
Make the create-order and payment endpoints idempotent. Have the frontend
generate a UUID idempotency key for each submission and send it in an
Idempotency-Key header. On the backend, store the key with the response in
the database using a unique constraint. If the same key comes in again,
return the original response instead of processing it again. Disable the
submit button while the request is pending.
2. The Stripe prompt
Integrate Stripe payments using idempotency keys. Generate one key per
checkout attempt and reuse that same key on every retry of that attempt.
Never create a new key for a retry.
3. The audit prompt (run this on an existing app)
Review every endpoint that creates, charges, refunds or sends something.
List which ones are not retry-safe, then add idempotency handling to each.
4. The webhook prompt
Make our webhook handlers idempotent. Store each event ID after processing
it, and skip any event ID we've already handled.
5. The test prompt
Write a test that sends the same payment request twice with the same
idempotency key and confirms only one charge is created and both calls
return the same response.
Common idempotency mistakes to avoid
Most bugs come from making a new key when you should reuse the old one.
- New key on every retry. If the retry gets a fresh key, the server thinks it's a new payment. The whole system is defeated.
- Disabling the button and calling it done. A disabled button stops double-clicks, not network retries or page refreshes. You still need the backend check.
- Checking without a unique constraint. Two requests arriving at the same instant can both slip past a simple "have I seen this?" check.
- Saving the key before the work finishes. If the server crashes halfway, you may block the retry that would have fixed it. Save the key and the result together.
- Using personal data as the key. An email or phone number isn't unique per action, and it leaks private info into logs.
- Keeping keys forever. Storage grows without limit. Set an expiry, like Stripe's 24-hour minimum.
- Forgetting webhooks and background jobs. These retry on their own, often more than your users do.
Jargon decoder: every term in this article, explained simply
Each term gets one plain sentence and one everyday picture.
| Term | What it actually means | Everyday picture |
|---|---|---|
| Idempotent | Doing it again changes nothing | Pressing an elevator button that's already lit |
| Idempotency key | A unique ID that marks one specific action | A coat-check ticket |
| UUID | A random ID so long that two will practically never match | A lottery number nobody else will ever draw |
| Request | A message your app sends to a server asking it to do something | Placing an order at a counter |
| Response | The server's reply | Your receipt |
| Retry | Sending the same request again because no reply came back | Re-asking "did you get my order?" |
| Timeout | Giving up on waiting for a reply after a set time | Hanging up after 30 seconds of ringing |
| Header | Extra info attached to a request, outside the main message | The label on the outside of a parcel |
| Endpoint | A specific address on a server that does one job | One service window at the post office |
| Cached result | A saved copy of an earlier answer, reused instead of redoing the work | Handing over yesterday's photocopy instead of copying again |
| Unique constraint | A database rule that blocks two identical values | A guest list that won't take the same name twice |
| Race condition | A bug when two things happen at the same moment and step on each other | Two people grabbing the last donut at once |
| Webhook | A message another service sends you automatically when something happens | A text alert from your bank |
| Backend | The server side, where the real work and data live | The restaurant kitchen |
| Frontend | The part users see and tap | The dining room and menu |
FAQ: idempotency and idempotency keys
What is idempotency in simple terms?
Idempotency means repeating an action has the same effect as doing it once. Press an elevator button five times and you still get one elevator.
What is an idempotency key?
An idempotency key is a unique ID attached to a request. The server uses it to spot duplicates and return the original result instead of repeating the action.
How do idempotency keys prevent double payments?
The server saves each key with its result. When a retry arrives with the same key, it returns the saved result and skips the charge.
Is POST idempotent?
No. Each POST request can create something new, like another order. That's why payment APIs such as Stripe accept idempotency keys on POST requests.
Are GET, PUT and DELETE idempotent?
Yes. Reading, replacing with an exact version, or deleting something gives the same end state no matter how often you repeat it.
How long does Stripe keep idempotency keys?
Stripe keeps keys for at least 24 hours and may remove them after that. A reused key after removal is treated as a new request.
What should I use as an idempotency key?
A UUID generated once per user action. Avoid emails, phone numbers or anything personal.
Is idempotency frontend or backend?
Both, but mainly backend. The frontend creates the key and disables the button. The backend checks the key. The database enforces it with a unique constraint.
Which programming language supports idempotency?
All of them. Idempotency is a design principle, not a language feature.
Do I need idempotency if I disable the Pay button after one click?
Yes. A disabled button stops double-clicks, but not automatic retries, page refreshes or lost network replies.
Key takeaways
- Idempotent = safe to repeat. Once or ten times, same result.
- Networks fail, users double-tap. Without idempotency, every retry risks a duplicate charge or order.
- An idempotency key is a coat-check ticket. One key per real action, reused on every retry.
- Backend checks, database enforces, frontend creates. All three layers have a job.
- Ask your AI coder for it by name. It won't add idempotency on its own, so put it in the prompt for anything that creates, charges or sends.

Top comments (0)