How to Test SendGrid Webhooks Locally
What Webhooks Does SendGrid Send?
SendGrid's Event Webhook notifies your application about the lifecycle of every email you send. Events include processed (email accepted by SendGrid), delivered (accepted by receiving server), open (recipient opened the email), click (recipient clicked a link), bounce (email bounced), dropped (SendGrid decided not to deliver), deferred (temporary delivery failure), spam_report, and unsubscribe.
SendGrid batches events — a single webhook request often contains an array of multiple events rather than one event per request. Each event in the array has a timestamp, email, event type, sg_message_id, and event-specific fields. For example, click events include the url that was clicked, and bounce events include the reason and type (hard vs soft bounce).
This batched format means a single request to your endpoint might contain dozens of events. The ReqPour dashboard handles this well, letting you expand the JSON array and inspect individual events.
Setting Up SendGrid Event Webhooks
In the SendGrid dashboard, go to Settings > Mail Settings > Event Webhook. Enter your ReqPour URL as the HTTP POST URL, for example https://abc123.reqpour.com/sendgrid/events. Select which events you want to receive — during development, enable all of them.
SendGrid also supports a signed event webhook using Elliptic Curve Digital Signature Algorithm (ECDSA). Enable "Signed Event Webhook" to receive the X-Twilio-Email-Event-Webhook-Signature and X-Twilio-Email-Event-Webhook-Timestamp headers for verification.
After saving, send a test email through SendGrid and watch the events arrive at your ReqPour endpoint. You will see a processed event first, followed by delivered (or bounce), and eventually open and click events as the recipient interacts with the email.
Processing SendGrid Events in Code
Here is a handler that processes the batched event format:
app.post('/sendgrid/events', express.json(), (req, res) => {
const events = req.body; // Array of event objects for (const event of events) {
switch (event.event) {
case 'delivered':
console.log(Delivered to ${event.email});
updateEmailStatus(event.sg_message_id, 'delivered');
break;
case 'open':
console.log(Opened by ${event.email});
trackOpen(event.sg_message_id, event.timestamp);
break;
case 'bounce':
console.log(Bounced: ${event.email} - ${event.reason});
handleBounce(event.email, event.type);
break;
case 'spam_report':
console.log(Spam report from ${event.email});
suppressEmail(event.email);
break;
}
}
res.status(200).send('OK'); }); ```
Always return a 200 status quickly. SendGrid retries on failures and may eventually suspend your webhook if too many requests fail. Process events asynchronously for anything that involves database writes or external API calls.
Debugging Email Flows with ReqPour
Email event debugging is tricky because events arrive asynchronously — a delivered event might come minutes after sending, and open/click events can arrive hours or days later. ReqPour's persistent request history lets you review events that arrived while you were away from your desk.
Use the search feature to filter events by email address or event type. For example, search for "bounce" to find all bounce events and examine the reasons. This helps you build robust bounce handling before going to production.
The replay feature is particularly valuable for email webhooks. Capture a bounce event once, then replay it repeatedly while you refine your bounce handling logic — updating suppression lists, sending notifications, or triggering re-verification flows. No need to actually trigger bounces by sending to invalid addresses.
SendGrid Webhook Best Practices
Handle the batched format correctly — do not assume each request contains a single event. Process the entire array and handle partial failures gracefully. If processing one event fails, do not return a 500 status; log the error and continue processing the rest.
Implement idempotency using sg_message_id and event type as a composite key. SendGrid may deliver the same event batch more than once, and your handler should not double-count opens or process the same bounce twice.
For production, verify webhook signatures to ensure requests genuinely come from SendGrid. During development with ReqPour, you can inspect the signature headers in the dashboard and compare them with your verification logic. The @sendgrid/eventwebhook npm package provides helpers for ECDSA verification.
Related
Get started with ReqPour
Catch, inspect, and relay webhooks to localhost. Free to start, $3/mo for Pro.