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.
Connection-scoped webhook deduplication pattern.
Gateway deduplication can reduce repeated delivery noise, but the receiver should still guard the business side effect with an idempotency key.

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.

ProviderGood keyWhy
Stripeevent.idStable across retry and manual resend.
GitHubX-GitHub-DeliveryUseful for tracing one provider delivery.
ShopifyX-Shopify-Webhook-IdDelivery id exposed in provider headers.
Customevent_id + event_typeStable 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.

Idempotent receiver
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.

Related guides