What is a idempotency key?
An idempotency key is a unique identifier — usually the event ID — used to deduplicate retried webhooks or HTTP requests. For inbound webhooks, use the source-issued event ID (`evt_...`, GitHub's `delivery-id`); never generate your own UUID inside the handler since each retry would compute a fresh one. Stripe's API uses idempotency keys explicitly via the `Idempotency-Key` header — propagate the webhook's event ID into downstream API calls and you get end-to-end safety with one key. Three rules: globally unique per logical operation, stable across retries, and TTL-bounded retention (24h-7d typical for webhooks).
The idempotency key is the value your handler keys its dedup-store on. For inbound webhooks, the right choice is the source-issued event ID (evt_..., delivery-id, etc.). For outbound HTTP requests you make in response to a webhook, the right choice is also usually that same event ID — propagated as an Idempotency-Key header so the downstream service can dedupe too.
Why the source's event ID is the right key: it's globally unique, generated once per event, identical across retries, and meaningful in the source's logs. Generating your own UUID inside the handler defeats the purpose — every retry computes a fresh UUID and looks like a new event. Don't do that.
Stripe's API uses idempotency keys explicitly: any POST request can include an Idempotency-Key header, and Stripe will return the cached result if the same key is replayed within 24 hours. Combining this with webhook handling looks like:
``
function onPaymentSucceeded(evt) {
// evt.id = 'evt_1MnH...' is the webhook event ID.
await stripe.refunds.create(
{ payment_intent: evt.data.object.id },
{ idempotencyKey: evt.id }
)
}
``
If the webhook is retried and the handler re-runs, the idempotency key on the Stripe call is the same, and Stripe returns the original refund record without creating a second one. End-to-end safety with one key.
Idempotency key design rules:
- Globally unique per logical operation. Two operations with the same business meaning should share a key; two operations with different meanings should not. - Stable across retries. The key for "process event X" must not change if you retry the handler. - Bounded retention at the receiver. Idempotency stores have memory; design with a TTL (24h-7d typical for webhook event IDs). - Fail-safe on key collision. If two genuinely different operations somehow share a key (UUID collision, bug in key generation), the system should fail closed — return an error — rather than silently treat the second as a duplicate.
Common mistake: using a hash of the request body as the idempotency key. This works for *exact* duplicates but treats two semantically equivalent requests with different formatting (one with trailing whitespace, one without) as different. Always prefer source-issued IDs.
For internal microservice traffic where there's no upstream-issued event ID, generate the key at the entry point of the request flow and propagate it through every downstream call. The same key flows from the load balancer to the auth service to the database write — every layer dedupes on the same value.
See Idempotency Key in real traffic
WebhookWhisper captures every webhook with full headers, body, signature, and timing — so concepts like idempotency key stop being abstract and become something you can inspect.
Start Free