All engineering notes
Integration engineering

How to build webhook consumers that survive retries, duplicates, and outages

Published 19 May 2026Updated 19 August 20269 min read

Short answer

An idempotent webhook consumer verifies the signature before parsing, persists the raw event under a unique key derived from the provider's event id, returns 2xx immediately, and processes asynchronously. Duplicate deliveries become no-ops through a uniqueness constraint, and every event stays replayable from the stored payload.

Verify, then persist, then acknowledge

Signature verification happens on the raw request body, before any parsing or normalisation, using a constant-time comparison. Any framework that reads and re-serialises the body before you verify will break the signature and tempt someone to skip verification.

Once verified, write the raw payload to storage with a unique constraint on the provider's event id, then return 2xx. Providers time out generously but not indefinitely, and slow synchronous processing is the most common cause of duplicate deliveries in the first place.

Idempotency in practice

  • Key on the provider event id where one exists; otherwise hash the stable fields of the payload.
  • Let the database enforce uniqueness. Application-level 'have I seen this?' checks race under concurrency.
  • Make downstream effects idempotent too: upserts rather than inserts, and external calls carrying their own idempotency keys.

Ordering is not guaranteed

Assume events arrive out of order. Carry a version or timestamp from the provider and discard state transitions that move backwards. Where ordering genuinely matters, serialise per entity — one queue partition per record key — rather than globally, which destroys throughput.

Failure handling that a human can use

Retries need bounded attempts with exponential back-off and jitter, then a dead-letter store that keeps the full payload and the failure reason. A dead-letter queue nobody can inspect or replay from the UI is a silent data-loss mechanism.

Instrument three numbers: events received, events processed, and backlog age. Backlog age is the one that predicts incidents.

Frequently asked questions

What is an idempotency key in a webhook consumer?

A stable identifier for a single logical event — usually the provider's event id, or a hash of the payload's immutable fields — stored with a uniqueness constraint so a duplicate delivery is rejected at the database rather than processed twice.

Should webhooks be processed synchronously?

No. Verify, persist, return 2xx, and process asynchronously. Synchronous processing couples your throughput to the provider's timeout and turns any slow downstream call into duplicate deliveries.

How do you replay webhook events safely?

Keep the verified raw payload and reprocess from it. Because handlers are idempotent, replaying a range of events converges on the same state instead of duplicating side effects.