Webhooks are the simplest possible integration: when something happens, we POST it to your URL. The simplicity hides a hard distributed-systems problem. Your endpoint will be down sometimes. The network will drop sometimes. And "at least once" delivery means you will occasionally receive the same event twice.
Sign every payload
Before you trust a webhook, prove it came from us. Each delivery carries a signature computed over the body with a shared secret. Verify it on arrival and reject anything that does not match. This closes the door on spoofed events hitting your processing path.
Retry with backoff
A single failed POST should not lose an event. A good policy retries on failure with exponential backoff and jitter, over a window long enough to ride out a short outage.
- Retry on connection errors and 5xx responses.
- Back off exponentially so you do not hammer a recovering service.
- Stop retrying on 4xx — those signal a problem only you can fix.
Make processing idempotent
Because delivery is at-least-once, your handler must be safe to run twice with the same event. The standard tool is an idempotency key.
// reject duplicates by event id const id = event.id; if (await seen.has(id)) return ok(); await process(event); await seen.add(id);
Keep a replay button
Even with retries, events sometimes need to be re-sent on purpose — you shipped a bug, or a downstream system was misconfigured. Searchable delivery logs plus one-click replay turn a stressful incident into a routine fix.
Signatures for trust, retries for transient failure, idempotency for duplicates, replay for everything else.
The takeaway
Reliable webhooks are not about never failing — they are about failing safely. Sign, retry, dedupe, and keep a replay path, and inbound email events become something you can build on with confidence.