Field notes
How SendGrid behaves, and where it bites
Many events per request, signed with a key pair rather than a shared secret.
How SendGrid sends
SendGrid POSTs a JSON array of event objects, each with its own event and recipient, and signs the request with ECDSA in X-Twilio-Email-Event-Webhook-Signature alongside a timestamp header.
Where it bites
- One delivery is many events. Losing a single POST can mean losing dozens of bounces or opens, and none of them will be resent individually.
- It is a key pair, not a secret. You hold the public verification key, which trips up anything expecting an HMAC secret.
- There is no one event name for the request. The array holds a mix, so labelling and searching happen inside the body rather than at the top of it.
The vault under it
Point a WebhookVault endpoint at SendGrid and every delivery is stored in full: headers verbatim, bodies byte-for-byte, labelled with its event names and searchable. Forward it onward to your own handler if you want the relay pattern, replay any delivery once the bug is fixed, and let a Watchdog notice the day it stops arriving.
Create an endpoint
Name it sendgrid-events. Turn on forwarding to your own handler if you want the vault in front of it rather than beside it.
Register the capture URL
SendGrid → Settings → Mail Settings → Event Webhook. Paste the capture URL, choose the events, and enable signature verification.
Keep everything
Every delivery from here on is on the shelf: searchable, replayable, and watched for the day it goes quiet.
Verify from the record, not just at the door
SendGrid signs the timestamp followed by the body with an ECDSA key pair, and publishes the public half for you to check against. The vault verifies every capture with it.
The vault stores the signature headers verbatim with every request, so a delivery from last month can still be checked today. Verdicts are part of Solo and up. How verification works →
# headers, stored verbatim "X-Twilio-Email-Event-Webhook-Signature": "MEUCIQ…" "X-Twilio-Email-Event-Webhook-Timestamp": "1757030400" # body is an ARRAY, one object per event [ { "email": "[email protected]", "event": "delivered" }, { "email": "[email protected]", "event": "bounce", "reason": "550 5.1.1 unknown recipient" }, { "email": "[email protected]", "event": "open" } ]
Event catalogue
SendGrid webhook events you'll see
A few of the event names SendGrid sends, not the full list: its own documentation has every one. A single POST carries an array of them, so the vault labels each capture with the names inside.
| Event | What it tells you |
|---|---|
| delivered | The receiving server accepted the message. |
| deferred | The receiving server delayed the message; SendGrid will try again. |
| bounce | The receiving server refused the message permanently. |
| dropped | SendGrid did not send it, for example because the address is on a suppression list. |
| spamreport | The recipient marked the message as spam. |
| unsubscribe | The recipient unsubscribed. |
Questions
SendGrid webhook questions, answered
The three that come up most when SendGrid is wired to a new endpoint.
Why does one SendGrid POST contain many events?
The Event Webhook batches. A single request is a JSON array with one object per event, often for several recipients. That is why one lost POST can mean dozens of lost bounces or opens, and why the vault stores the whole array as one capture.
How do I verify the SendGrid Event Webhook signature?
Turn on signature verification in Mail Settings, copy the public key SendGrid shows you, and verify the ECDSA signature in X-Twilio-Email-Event-Webhook-Signature over the timestamp header followed by the raw body. Use the exact bytes received: re-serialised JSON or trimmed whitespace fails. On Solo and up the vault does this at capture.
How does SendGrid retry a failed Event Webhook?
If your endpoint does not answer with a 2xx, SendGrid keeps retrying the batch for up to 24 hours. A WebhookVault capture URL answers as soon as the batch is stored, so what SendGrid sent is kept even while your own handler is failing.
One POST, dozens of events, all kept.
Free tier, no card. Every batch stored whole; search inside it starts on Pro.
Sign up with GitHub, Google or email. Two-factor authentication is required on every account, Free included, for account security.