Webhook Guides
Duplicate Webhook Events
Duplicate webhook events are normal in production. Stripe, GitHub, Shopify, Okta, Slack, and internal systems can all send the same business event more than once when a receiver times out, returns an error, or an operator triggers replay after an outage.
The goal is not to pretend duplicates will never happen. The goal is to make duplicates visible, route them safely, and keep the receiver from applying the same side effect twice.
Common causes
- The provider did not receive a fast 2xx response.
- The receiver timed out after partially applying the side effect.
- The provider retried after a network or TLS failure.
- An operator manually resent a provider event.
- A gateway replay or event retry redelivered stored traffic.
- Two destination branches intentionally received the same original request.
How duplicates look in logs
| Signal | What it means | Action |
|---|---|---|
| Same provider event id | Same business event delivered again. | Return success without repeating side effects. |
| Same payload, new delivery id | Provider retry or manual redelivery. | Inspect previous attempt status. |
| Same FastHook event retry | Gateway retried one destination branch. | Confirm receiver idempotency. |
| Many failures, then replay | Outage recovery window. | Throttle destination and replay narrowly. |
Receiver guard
Store the idempotency key before the business mutation. If the key already exists, return success and keep the previous processing result visible for debugging.
Duplicate guard
async function processWebhook(event) {
const idempotencyKey = event.id || event.headers["x-provider-delivery"];
const firstSeen = await store.reserveOnce(idempotencyKey);
if (!firstSeen) {
return { ok: true, duplicate: true };
}
await applySideEffect(event);
await store.markProcessed(idempotencyKey);
return { ok: true };
}