Webhook Guides
Webhook Idempotency
Webhook idempotency is the difference between safe recovery and accidental duplicate side effects. Providers retry webhooks, operators replay events, and receivers can time out after partially completing work. Your system should be able to see the same event twice without charging a customer twice, creating two tickets, or provisioning duplicate access.
FastHook helps by storing inbound requests, routing events through connections, showing every destination attempt, and providing deduplication rules. The final safety boundary still belongs in the receiver that changes business state.
Why duplicate webhook events happen
- The provider retries because your endpoint timed out or returned a non-2xx response.
- Your receiver completed the side effect but failed before returning the provider response.
- An operator manually redelivers a provider event during an incident.
- A gateway replay sends a stored event after the destination is fixed.
- Two destination branches process the same business event for different consumers.
Choose an idempotency key
The best idempotency key is a provider event identifier that stays stable across retries and manual redelivery. If the provider does not expose a clear event id, use a stable business object id plus event type. Avoid using timestamps alone because retries often carry new delivery timestamps for the same business change.
| Provider | Good key | Why |
|---|---|---|
| Stripe | event.id | Stable across retry and manual resend. |
| GitHub | X-GitHub-Delivery | Useful for tracing one provider delivery. |
| Shopify | X-Shopify-Webhook-Id | Delivery id exposed in provider headers. |
| Custom | event_id + event_type | Stable business key when provider ids are not standardized. |
Receiver-side idempotency pattern
The receiver should reserve the idempotency key before applying the side effect. If the key already exists, return success without repeating the action. Store enough metadata to inspect the previous outcome.
async function handleWebhook(event) {
const key = event.id;
const inserted = await db.idempotencyKeys.insertOnce({
key,
provider: "stripe",
eventType: event.type,
receivedAt: new Date()
});
if (!inserted) {
return { ok: true, duplicate: true };
}
await applyBusinessSideEffect(event);
await db.idempotencyKeys.markProcessed(key);
return { ok: true };
}Where FastHook fits
FastHook can deduplicate at the connection level, filter noisy events before they reach fragile receivers, and show whether a duplicate came from provider retry, manual retry, or bulk replay. That helps operators recover without guessing, but it should complement receiver-side idempotency rather than replace it.