Email API Webhook Reliability: Authenticate, Persist and Reconcile
Build a reliable email webhook consumer with raw-body verification, durable event storage, duplicate handling and clear message-state transitions.
TL;DR
- Treat webhooks as evidentiary inputs: authenticate incoming requests, persist the observed event, and reconcile it with local message state so callbacks become reliable evidence rather than blind instructions.
- Ingest reliably by designing a small public edge and an ingestion pipeline that verifies signatures, validates schema, persists a durable record, then lets workers perform downstream business processing idempotently.
- Ensure correctness by persisting before acknowledging and by testing the full path (signatures, altered bodies, duplicates, outages) so delivery retries and replays are handled safely.
A webhook is evidence, not an instruction to trust blindly
An email webhook tells your application about an event observed by a provider. It can update delivery evidence, trigger suppression handling or add an inbound message to a conversation. Because it arrives over a public endpoint, the application must authenticate it and constrain what it is allowed to change.
Design the consumer as an ingestion pipeline: verify the request, validate the event shape, persist a durable record, then process the business consequence. Keep that sequence clear so a temporary processing failure does not lose the only copy your application received.
Providers define their own signatures, timestamps, retries and event fields. This article proposes a consumer architecture; it does not assume that every service delivers events in the same order or uses the same verification scheme.
Verify the bytes that were signed
For SendDart, the SDK documentation instructs webhook verification against the raw request body and supplied headers using the signing secret. Parsing and reserializing JSON before verification can change the bytes and break the signature check. SendDart webhook documentation.
Configure the route so middleware preserves the raw body needed by the verifier. Apply body-size limits before accepting unbounded content. Load the secret from trusted server configuration and avoid printing it or the full private payload in error logs.
Use the documented freshness policy rather than disabling timestamp checks because a test failed. If clocks or delayed delivery create problems, investigate the actual cause and the provider's supported replay behavior. Authentication and duplicate handling solve different problems; a valid signature does not make repeated processing safe.
Persist before acknowledging completion
After verification and schema validation, insert the event into a durable inbox table or queue. Use a unique provider event identity where available and include account or tenant scope. Then acknowledge according to the provider's expected contract.
The durable event record can be processed by a worker, allowing the HTTP endpoint to remain small and predictable. If the database write fails, do not return a success response that falsely implies your application retained the event. Align the response behavior with the provider's documented retry policy.
Avoid performing slow downstream work before persistence. Sending a support alert, updating analytics and fetching an attachment in the request path can increase timeout risk. Store the authenticated evidence first, then let bounded workers handle those consequences.
Make duplicate processing harmless
A duplicate event should resolve to the same stored observation rather than repeat every side effect. Enforce uniqueness at the database boundary where possible. Application-only checks can race when two deliveries arrive at nearly the same time.
Side effects also need their own idempotency. If a bounce event creates a suppression record and an operational alert, repeating the worker should not create multiple alerts or reverse a newer preference decision. Record processing status and use stable operation identities for downstream actions.
Resend's documentation explicitly exposes webhook replay and verification capabilities, illustrating why consumers should expect intentional reprocessing as well as ordinary retries. The exact contract remains provider-specific. Resend webhook introduction.
Reconcile events with message history
Store provider name and message ID alongside the application's notification ID. An event can arrive before the send response has been committed locally, so a temporary unmatched state may be necessary. Retry correlation with a bounded policy instead of discarding the event or attaching it to a guessed recipient record.
Define state transitions based on evidence. An older acceptance event should not erase a later delivery failure. A delivery event describes recipient-server acceptance, not proof of reading. Keep timestamps and event types so the support interface can show a timeline rather than forcing every observation into one mutable label.
For multi-tenant systems, derive the authorized tenant from trusted integration configuration and stored message ownership. Do not trust an arbitrary tenant identifier inside an unauthenticated or insufficiently validated payload to select data for mutation.
Related reading: Scheduled Email Delivery: Store the Time, Content and Cancellation State.
Separate suppression from analytics
Some events require operational action. A complaint or permanent failure can affect whether future mail should be attempted. Other events, such as opens or clicks, may be useful analytics but should not automatically grant business authorization.
Document the scope of suppression decisions and preserve the original reason. A replayed complaint should not be able to re-enable a recipient because another branch of the handler writes a default status. Test transitions across duplicate and delayed observations.
Minimize retained content. Delivery evidence usually needs identifiers, event types and timestamps, not complete reset URLs or confidential message bodies. Give support a useful timeline while restricting access to sensitive inbound content and attachments.
Test the whole ingestion path
Build fixtures for a valid signature, invalid signature, altered body, stale timestamp and duplicate event. Test a database outage before acknowledgment and a worker crash after persistence. Confirm that recovery processes the retained record without repeating side effects.
Also test an unknown message ID and an event from the wrong integration scope. Those should produce controlled pending or rejected outcomes, not arbitrary updates. Add a malformed but authenticated payload so schema validation is exercised independently of signature verification.
Use the provider's documented test capability for a controlled integration check, and inspect its result carefully. A successful API request to trigger a test is not necessarily proof that the destination accepted the webhook. Keep transport success and application processing success visible as separate evidence.
A reliable webhook consumer is small at the public edge and deliberate behind it. Authenticate the original request, retain the observation, process it idempotently and reconcile it with the correct message. That turns callbacks into dependable evidence rather than another source of hidden duplicates.
Related reading: Best Email API: Choose With a Production Acceptance Test.