Debug Duplicate Webhook Deliveries
Correlate webhook delivery ids and timestamps to distinguish provider retries from duplicate events emitted by your own pipeline.
Task
Use delivery-id deduplication and timestamp correlation to locate the source of repeated webhook processing.
When to use this
Orders are created twice because a webhook appears to fire more than once, but it is unclear whether the provider retried or the consumer reprocessed.
Prerequisites
- Webhook events stored with raw headers and bodies
- A unique delivery id in the provider payload
- Query access to the consumer database
Steps
1. Deduplicate by provider delivery id before any business logic runs.
const seen = new Set();
for (const event of events) {
if (seen.has(event.deliveryId)) continue;
seen.add(event.deliveryId);
await process(event);
}Expected output: Each provider delivery id is processed at most once.
2. Correlate repeated deliveries by timestamp to tell retries from re-emits.
SELECT delivery_id, count(*) AS attempts, max(received_at) - min(received_at) AS window
FROM webhook_events
GROUP BY delivery_id
HAVING count(*) > 1
ORDER BY window DESC;Expected output: Delivery ids repeated within the provider retry window indicate genuine retries rather than new events.
Troubleshooting
Problem
Delivery id is missing on legacy payloads
Solution
Fall back to a hash of body plus event type, and backfill ids once the provider adds them.
Problem
Business logic runs before dedup
Solution
Move the dedup check into the ingestion boundary so side effects never happen twice.
Limitations
- Requires the provider to expose a stable delivery id.
- Dedup store must be durable and shared across consumers.
- Does not address providers that emit genuinely distinct duplicate events.
What this recipe does
Use delivery-id deduplication and timestamp correlation to locate the source of repeated webhook processing.
Orders are created twice because a webhook appears to fire more than once, but it is unclear whether the provider retried or the consumer reprocessed.
Prerequisites
- Webhook events stored with raw headers and bodies
- A unique delivery id in the provider payload
- Query access to the consumer database
Procedure
- Deduplicate by provider delivery id before any business logic runs.
const seen = new Set();
for (const event of events) {
if (seen.has(event.deliveryId)) continue;
seen.add(event.deliveryId);
await process(event);
}
Expected result: Each provider delivery id is processed at most once.
- Correlate repeated deliveries by timestamp to tell retries from re-emits.
SELECT delivery_id, count(*) AS attempts, max(received_at) - min(received_at) AS window
FROM webhook_events
GROUP BY delivery_id
HAVING count(*) > 1
ORDER BY window DESC;
Expected result: Delivery ids repeated within the provider retry window indicate genuine retries rather than new events.
How to confirm it worked
Run the commands above and confirm every expected result in order. If a step does not produce the stated output, use the troubleshooting notes before continuing. A passing run shows the count of repeated business rows drops to zero and the correlation query shows only expected provider retries.
Troubleshooting
Delivery id is missing on legacy payloads — Fall back to a hash of body plus event type, and backfill ids once the provider adds them.
Business logic runs before dedup — Move the dedup check into the ingestion boundary so side effects never happen twice.
Limitations
- Requires the provider to expose a stable delivery id.
- Dedup store must be durable and shared across consumers.
- Does not address providers that emit genuinely distinct duplicate events.