Glossary
Idempotency
The property that processing the same webhook twice has the same effect as processing it once. Critical because providers retry deliveries.
Webhook providers retry failed deliveries — typically with exponential backoff over hours or days. They also occasionally double-deliver successful webhooks (network races, ack loss). If your handler isn't idempotent, retries cause double charges, duplicate emails, duplicate inventory deductions.
The standard pattern: every event has a unique ID (e.g., Stripe's evt_*, GitHub's X-GitHub-Delivery). Store processed IDs in a database with a unique constraint. On every incoming event: try to insert the ID — if it conflicts, return 200 immediately without re-running side effects.
Idempotency goes beyond dedup: if your handler partially fails and the provider retries, the second attempt should reconcile state, not re-do completed work. Design handlers as state machines, not scripts.
Idempotent handler using event ID as dedup key
async function handle(event: WebhookEvent) {
const { rowCount } = await db.query(
"INSERT INTO processed_events (id) VALUES ($1) ON CONFLICT DO NOTHING",
[event.id],
);
if (rowCount === 0) return; // already processed
await applyBusinessLogic(event);
}How HookSense helps
HookSense's Replay feature is a great way to test idempotency: capture an event, replay it 10× against your handler, and confirm the side effects only happen once.
Get a free webhook URL