
Here's a scenario every backend engineer eventually lives through: a client sends a "charge $50" request, the network hiccups, no response comes back, so the client retries — and now the customer has been charged twice. The request succeeded both times; the problem is that "retry on failure" and "don't do it twice" are in direct conflict. Idempotency is how you resolve that conflict, and idempotency keys are the mechanism that makes retries safe.
This guide explains idempotency from first principles — what it means, why retries make it essential, how idempotency keys work in practice, the difference between at-least-once and exactly-once delivery, and how it all ties into building reliable APIs and webhooks.
What Idempotency Means
An operation is idempotent if performing it multiple times has the same effect as performing it once. Turning a light switch to "on" is idempotent — flip it once or five times, the light is on. Adding $50 to a balance is not idempotent — do it five times and you've added $250.
In HTTP, some methods are idempotent by definition:
GET,PUT,DELETEare meant to be idempotent.PUT /users/42with the same body produces the same result no matter how many times you send it.DELETEtwice still leaves the resource deleted.POSTis not — it typically creates something, so sending it twice usually creates two things.
This matters because the unsafe operations (payments, orders, sending messages) are almost always POSTs. The whole challenge of safe retries is making those non-idempotent operations behave idempotently.
Why Retries Make This Unavoidable
You can't avoid retries in a distributed system, because of one brutal fact: when a request fails, the client often can't tell whether the server processed it. A timeout, a dropped connection, or a 5xx could mean:
- The request never arrived (safe to retry), or
- The request was processed but the response was lost (retrying duplicates it).
From the client's side these look identical. Since giving up on every failed request would make systems hopelessly fragile, clients retry — and retrying is exactly what causes duplicates. This is why anything that retries, including webhook delivery, payment APIs, and message queues, needs idempotency to be correct rather than merely lucky.
How Idempotency Keys Work
An idempotency key is a unique token the client generates and attaches to a request (commonly an Idempotency-Key header). It's the client saying: "this is logically the same operation; if you've seen this key before, don't do it again — just return the original result."
The server-side flow:
- Receive a request with an idempotency key.
- Look up the key in a store (a database table or cache like Redis).
- If the key is new: process the request, then save the key together with the response before replying.
- If the key already exists: skip the work entirely and return the stored response from the first time.
The result: the client can retry the same request as many times as it likes, and the operation happens exactly once. The retried calls get the same answer the original would have.
Key design details that separate a correct implementation from a subtly broken one:
- The client generates the key, usually a UUID, one per logical operation — not per HTTP attempt. All retries of the same operation reuse the same key.
- Store the key and the result atomically with the operation itself. If you process the charge but crash before recording the key, a retry will charge again. A database transaction (or a unique constraint on the key column) closes this gap.
- Handle concurrent duplicates. Two retries can arrive simultaneously; a unique constraint or a lock ensures only one wins and the other returns the stored result rather than double-processing.
- Expire keys after a sensible window (24 hours is common). Keep them long enough to cover all realistic retries, but don't store them forever.
At-Least-Once vs Exactly-Once Delivery
Most real systems — webhooks, message queues like Kafka or SQS, event buses — guarantee at-least-once delivery: they promise your message will arrive, and accept that it might arrive more than once. They choose this because the alternative, at-most-once, risks losing messages entirely, which is usually worse.
True exactly-once delivery across a network is famously close to impossible to guarantee at the transport layer. So the industry settled on a pragmatic pattern:
at-least-once delivery + idempotent processing = effectively exactly-once.
In other words, you don't try to make the network deliver exactly once. You let it deliver one-or-more times and make your processing idempotent so duplicates are harmless. This is the single most important idea for anyone building or consuming webhooks: assume duplicates will happen, and deduplicate on a stable ID (an event ID or idempotency key) on the receiving end.
Idempotency for Webhook Consumers
If you receive webhooks, the sender almost certainly retries on any non-2xx response or timeout — which means you will get the same event twice eventually. To handle it safely:
- Dedupe on the event ID. Most providers include a unique event ID; record processed IDs and ignore repeats.
- Acknowledge fast. Return
2xxquickly to stop the sender's retries, then do heavy work asynchronously — but only after you've durably recorded the event. - Make the handler idempotent, not just deduplicated — if the same event triggers a downstream action, that action needs the same protection.
The failure mode to avoid: doing the work, then timing out before sending 2xx. The sender retries, and a non-idempotent handler does the work twice. This is the webhook version of the double-charge problem, and it's why monitoring webhook delivery and retries matters — you need visibility into duplicates and failures, not just successes.
How Webalert Helps
Idempotency is something you implement in code, but its failure modes show up at runtime — and that's where monitoring earns its keep:
- Endpoint monitoring for the APIs and webhook receivers that depend on idempotent handling, so you know the moment they start failing or timing out.
- Catching the conditions that trigger retries — timeouts and 5xx errors — from the outside, before a wave of retries turns a blip into duplicated work.
- Content validation, so an endpoint that returns
200 OKbut silently mishandles duplicates gets surfaced. - Multi-region checks that confirm your endpoints are reachable and responsive, reducing the lost-response scenarios that make idempotency necessary in the first place.
Webalert won't write your idempotency layer, but it tells you fast when the failure conditions it exists to handle are actually happening.
Summary
Idempotency means an operation can be repeated without changing the result beyond the first time — and it's essential because retries are unavoidable, while clients can't tell a lost request from a lost response. Idempotency keys solve this: the client sends a unique token, the server records it with the result, and any retry returns the stored response instead of re-doing the work.
For delivery, stop chasing transport-level exactly-once and embrace the proven pattern: at-least-once delivery plus idempotent processing. Generate keys per logical operation, store them atomically with the work, handle concurrent duplicates, and dedupe webhooks on their event ID. Get this right and retries become a safety feature instead of a source of double-charges.