Email webhooks turn provider-side delivery events into application state. A good endpoint verifies the signature, acknowledges quickly, stores an immutable event record, and processes the business effect from a queue. The goal is not to do everything in the request handler. The goal is to never lose or double-apply an event.
last updated 2026-05-074 sections
section 01
Webhook payload structure
Most providers send a message identifier, event type, recipient, timestamp, and provider-specific metadata. Some send one event per request. Others batch several events into one payload. Normalize the incoming shape before downstream systems depend on it.
field
why it matters
storage rule
provider_event_id
Dedupes retries and replayed callbacks.
Unique index when available.
message_id
Connects delivery state to the original send.
Store with the outbound send row.
event_type
Routes bounce, complaint, delivery, open, and click events.
Normalize to internal enum.
raw_payload
Keeps audit data for provider disputes.
Store before processing.
section 02
Signature verification
Verify the signature before parsing business meaning from the payload. Different providers sign different material: raw body, timestamp plus body, or provider-specific fields. The endpoint should reject stale timestamps and never rebuild the signed body from parsed JSON.
okRead the raw request body before JSON parsing.
okVerify timestamp tolerance when the provider includes one.
okCompare signatures with a constant-time function.
okKeep test-mode secrets separate from production secrets.
okLog rejected signatures without storing sensitive payload fields.
section 03
Acknowledgment and retry behavior
Return a 2xx response after the event is authenticated and stored. Heavy work belongs in a queue. Providers retry failed callbacks, which means the endpoint must tolerate duplicate delivery and out-of-order arrival.
event
common action
idempotency need
delivered
Mark message as accepted by the mailbox provider.
Do not move a bounced message back to delivered.
hard_bounce
Suppress the address for future sends.
Apply once per recipient and stream.
complaint
Suppress and flag reputation risk.
Treat as terminal.
open or click
Record engagement signal if tracking is enabled.
Dedup high-volume repeats.
section 04
Processing and storage pattern
The safest pattern is receive, verify, store, enqueue, acknowledge. A worker can then apply state transitions, update suppression lists, notify support, or sync analytics without keeping the provider waiting.
okPersist raw payload and normalized fields in the same transaction.
okUse a unique event key to make replay harmless.
okProcess terminal states such as hard bounce and complaint before engagement states.
okAlert when webhook volume drops to zero for an active stream.