11 Dec, 2025
8 min read

When building modern applications, especially those involving payments, bookings, or any operation that must not happen twice; one of the biggest challenges is ensuring that repeated requests don’t cause duplicate actions.
Network failures, user impatience (“let me click again!”), mobile dropouts, and browser refreshes all create scenarios where the same API request may be sent multiple times. If your backend isn't prepared, you’ll end up with double charges, duplicate orders, or corrupted data.
This is where idempotency comes in.
Idempotency means that performing the same operation multiple times results in the same outcome. That is, no side effects beyond the first execution.
Think of it like a doorbell: Tap once, tap ten times The bell rings. It doesn’t install a new bell each time.
In APIs:
GET /users/123 is naturally idempotent — you get the same user every time.
DELETE /orders/10 should be idempotent — delete is delete, even if the client calls it twice.
POST /payments is not idempotent by default — multiple POSTs might create multiple charges.
To make non-idempotent actions safe, we introduce Idempotency Keys.

An idempotency key is a unique token generated by the client and included with an API request (usually in a header). The server stores the result of the first request associated with that key.
If the same key appears again, the server returns the original response instead of executing the action again. Example:
Post request with Idempotency Key
POST /checkout Idempotency-Key: 7f1c21fa-f772-4ef5-9b5a-0fb83adb19b5
If the client retries the exact same request with the same key:
The server does not create another order.
The server instead returns the original result.
Prevents double payments
Prevents duplicate database records
Makes retrying safe
Helps make APIs more reliable in mobile or unstable network environments
This ensures at-least-once delivery from the client but exactly-once execution on the server.
The server can make idempotency keys compulsory for some requests, by setting the idempotency_key request header as required, just like requiring an Authorization header.
app.post("/book-seat", async (req, res) => { const key = req.header("Idempotency-Key"); if (!key) { return res.status(400).json({ error: "Missing Idempotency-Key header" }); } // proceed with idempotency logic... });
This makes the API reject any write-operation that doesn’t include the key. This is the most common enforcement.
The API can accept requests without a key, but this configuration results in non-idempotent behavior.
In some APIs, idempotency is optional.
Clients that want safety include a key. Clients that don’t → take the risk.
This is typical in internal services where you fully control both client and server code.
Retries happen more often than even developers realize, and they can be caused by a number of reasons such as:
If the network drops during transmission:
If the user refreshes the page while a POST is still in-flight, the browser may:
cancel the original request
retry it automatically upon re-render
or your app logic might resend the action
Users click:
multiple times when the UI doesn't respond fast enough.
This is the most common cause of duplicate actions (charges, orders, bookings).
Many libraries include automatic retries:
React Query (default retry = 3 for failed requests)
Axios retry plugins
HTTP libraries on mobile (iOS/Android)
SWR
Apollo client (GraphQL)
Even devs can forget that these are enabled.
Even with backend idempotency, your frontend should also help reduce unnecessary repeated calls.
The frontend should generate a unique key before sending requests for:
Payments
Submissions
Checkout
Booking a seat
Creating a resource
Create Order post request with idempotency key
import { v4 as uuid } from "uuid"; async function createOrder(cart) { const key = uuid(); const res = await fetch("/api/checkout", { method: "POST", headers: { "Content-Type": "application/json", "Idempotency-Key": key, }, body: JSON.stringify(cart), }); return res.json(); }
Prevent “spam-clicking”
<button disabled={loading}> {loading ? "Processing..." : "Checkout"} </button>
If the user refreshes mid-checkout, store the idempotency key in:
localStorage, or
React Query’s cache
So if they retry, they reuse the same key.
This combination ensures that no matter how shaky the network gets, your operation executes only once.
Idempotency is one of the most underrated concepts in API design. It turns unreliable networks into predictable systems and protects users from accidentally triggering expensive or irreversible operations multiple times.
If your app involves anything transactional—bookings, purchases, reservations—idempotency keys are not optional. They're essential.
On the frontend, combine idempotency keys with:
safe retry logic
button disabling
exponential backoff
local caching of request state
And you'll have a resilient, user-friendly system that "just works," even when the network doesn't.
Feel free to reach out to me if you're looking for a developer, have a query, or simply want to connect.