Skip to main content
Webhook senders are at-least-once: on a network blip or a non-2xx response they retry, so the same event can arrive more than once. webhook.co deduplicates inbound requests per endpoint so a retry collapses into the event you already captured, instead of showing up (and metering) twice.
Deduplication is best-effort load reduction, not an exactly-once guarantee. Keep your own handler idempotent — dedup reduces duplicates, it doesn’t promise their absence.

The default

Every endpoint starts on the identifier mode with a 24-hour window. It keys each request on the first stable signal it finds:
  1. a Standard Webhooks webhook-id header,
  2. a recognized provider’s event id (header- or body-borne), then
  3. a content hash of the request — the HTTP method, a canonicalized path + query, and the body, within the time window.
Two identical retries collapse; two genuinely different requests stay distinct. This default needs no configuration.

Modes

Set a per-endpoint dedupConfig to change how the key is derived:
ModeKeys onUse it when
identifier (default)id ladder, then content hashthe sender supplies a stable id, or you want the safe default
contentthe canonical request content (method + path + query + body)the sender’s ids are unreliable or reused across distinct events
fieldsoperator-selected field pathsthe sender has no stable id but a stable field (e.g. body.data.id)
offnothing — every request is its own eventyou want to capture every request (testing, debugging)
Each mode (except off) takes a windowSeconds (60–604800). off disables collapsing entirely.
off means every request is a distinct, billable event — including retries and any non-webhook traffic (uptime checks, link previews) that reaches the URL. Use it deliberately.

Field paths

fields mode keys on values you select with field paths. A path starts with a root — headers, body, query, or path — and addresses into it with dot notation and array accessors:
  • body.data.id — a nested body value
  • headers.x-event-id — a header value
  • query.id — a query parameter
  • body.items[*].sku — every element of an array ([*] wildcard, or [0] for one index)
Provide include paths (the values that make up the key) and optionally exclude paths (volatile fields to drop, like a per-delivery timestamp). If a configured field is missing from a request, that request is treated as distinct rather than collapsed — webhook.co never silently drops an event it can’t key.

Configure it

Deduplication is configured identically across every surface — set it when you create an endpoint, or update it any time. --dedup-reset (CLI) or a null dedupConfig (API) returns an endpoint to the default. From the CLI:
wbhk endpoints create orders-prod --dedup-mode content --dedup-window 300
wbhk endpoints update <endpoint-id> --dedup-mode fields --dedup-field body.data.id
wbhk endpoints update <endpoint-id> --dedup-mode off
wbhk endpoints update <endpoint-id> --dedup-reset
From the API — set the config on create, or PATCH it later:
curl -X POST https://api.webhook.co/v1/endpoints \
  -H "authorization: Bearer $WBHK_API_KEY" -H "content-type: application/json" \
  -d '{"name":"orders-prod","dedupConfig":{"mode":"content","windowSeconds":300}}'

curl -X PATCH https://api.webhook.co/v1/endpoints/<endpoint-id> \
  -H "authorization: Bearer $WBHK_API_KEY" -H "content-type: application/json" \
  -d '{"dedupConfig":{"mode":"fields","windowSeconds":300,"fields":{"include":["body.data.id"]}}}'
From a typed SDK:
await webhook.endpoints.update({
  endpointId,
  dedupConfig: { mode: "content", windowSeconds: 300 },
});
Config changes take effect within the cache-propagation window (about five minutes), not instantly — and a change never claims exactly-once across the transition. Every change is recorded in your endpoint’s tamper-evident audit trail.