Webhook Integration Guide

Set up real-time email event notifications using webhooks. Automatically process bounces, deliveries, opens, clicks, and other events as they happen -- no polling required.

1. What Are Email Webhooks?

Webhooks are HTTP callbacks that notify your application in real time when email events occur. Instead of repeatedly polling an API to check for new events, webhooks push event data to your server the moment something happens.

How Webhooks Work

When an email event occurs (such as a bounce or a click), the sending platform makes an HTTP POST request to a URL you specify. Your server receives the event data as a JSON payload, processes it, and returns a 200 status code to acknowledge receipt.

Why Webhooks Matter

Webhooks let you react to email events instantly. Automatically suppress bounced addresses, track engagement metrics, trigger follow-up workflows, and keep your email lists clean without manual intervention or scheduled batch jobs.

2. SendGrid Event Types

SendGrid provides webhooks for a comprehensive set of email events. Below are the event types you can subscribe to and what each one indicates.

Event Description Recommended Action
bounce The receiving mail server permanently rejected the email. This is a hard bounce caused by an invalid address, non-existent domain, or a permanent delivery failure. Immediately remove the email address from your active list and add it to a suppression list. Continued sending to bounced addresses damages your sender reputation.
delivered The email was successfully accepted by the recipient's mail server. This does not guarantee inbox placement -- the server may still route the message to spam. Log the delivery for reporting. Use delivery rates to monitor the health of your sending infrastructure.
open The recipient opened the email. Tracked via a transparent pixel embedded in the HTML body. Note that some email clients block tracking pixels, so open rates undercount actual opens. Use open data to measure engagement. Segment your list by openers for targeted follow-up campaigns.
click The recipient clicked a link in the email. Link tracking must be enabled in your SendGrid settings for this event to fire. Track which links get the most clicks to optimize future email content and calls to action.
deferred The recipient's mail server temporarily rejected the email. SendGrid will automatically retry delivery for up to 72 hours. Common causes include rate limiting by the receiving server or temporary mailbox full conditions. Monitor deferred events for patterns. If a domain consistently defers, investigate whether your sending IP has a reputation issue with that domain.
dropped SendGrid did not attempt to deliver the email because the address is on a suppression list (previous bounce, spam report, or unsubscribe). No action needed. This is expected behavior. Review your suppression lists periodically to ensure they are accurate.
spam_report The recipient marked the email as spam. This is a strong negative signal from the recipient. Immediately suppress the address. High spam report rates can lead to your sending domain being blocklisted. Review your email content and sending frequency.
unsubscribe The recipient clicked the unsubscribe link in the email. This event fires when using SendGrid's built-in unsubscribe management. Remove the address from your active sending list. Honor unsubscribes promptly to comply with CAN-SPAM and GDPR regulations.

3. Webhook Payload Format

SendGrid sends webhook events as a JSON array in the POST body. Each element in the array represents a single event. Multiple events may be batched into a single request.

JSON Payload Example
[
    {
        "email": "user@example.com",
        "timestamp": 1712016000,
        "event": "bounce",
        "sg_event_id": "abc123def456",
        "sg_message_id": "msg_789.filter0001.sendgrid.net",
        "reason": "550 5.1.1 The email account does not exist",
        "status": "5.1.1",
        "type": "bounce",
        "ip": "168.245.10.1",
        "tls": 1,
        "cert_err": 0
    },
    {
        "email": "another@example.com",
        "timestamp": 1712016005,
        "event": "delivered",
        "sg_event_id": "xyz789abc012",
        "sg_message_id": "msg_456.filter0001.sendgrid.net",
        "response": "250 2.0.0 OK",
        "ip": "168.245.10.1",
        "tls": 1,
        "cert_err": 0
    }
]

Common Payload Fields

Field Type Description
email string The recipient email address associated with the event.
timestamp integer Unix timestamp (seconds) of when the event occurred.
event string The event type: bounce, delivered, open, click, deferred, dropped, spam_report, or unsubscribe.
sg_event_id string A unique identifier for this specific event. Use this for deduplication.
sg_message_id string The unique identifier of the original email message.
reason string Present on bounce events. Contains the SMTP error message from the receiving server.
url string Present on click events. The URL that the recipient clicked.

4. Setting Up Your Webhook Endpoint

Follow these steps to configure SendGrid to send webhook events to your server.

1

Create an HTTPS Endpoint

Set up a publicly accessible HTTPS endpoint on your server (for example, https://yourapp.com/webhooks/sendgrid). The endpoint must accept POST requests with a JSON body. It must be served over HTTPS -- SendGrid will not deliver webhooks to HTTP URLs.

2

Configure SendGrid Event Webhook

In the SendGrid dashboard, navigate to Settings > Mail Settings > Event Webhook. Enter your endpoint URL and select the event types you want to receive (bounce, delivered, open, click, deferred, dropped, spam report, unsubscribe). Enable the webhook and save.

3

Return a 2xx Response

Your endpoint must return a 200 or 204 status code within 3 seconds to acknowledge receipt. If SendGrid does not receive a 2xx response, it will retry the delivery with exponential backoff for up to 24 hours.

4

Test Your Endpoint

Use the Test Your Integration button in the SendGrid webhook settings to send a sample payload. Verify that your server receives the payload, parses it correctly, and returns a 200 status. Check your server logs to confirm the events were processed.

5. Code Examples for Processing Events

Below are production-ready examples showing how to receive and process SendGrid webhook events in popular server-side languages.

Node.js with Express

This Express handler receives webhook events, processes bounces by adding them to a suppression list, and logs all other events for analytics.

JavaScript
const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhooks/sendgrid', (req, res) => {
    const events = req.body;

    if (!Array.isArray(events)) {
        return res.status(400).send('Invalid payload');
    }

    for (const event of events) {
        switch (event.event) {
            case 'bounce':
                // Add to suppression list immediately
                console.log(`BOUNCE: ${event.email} - ${event.reason}`);
                suppressEmail(event.email, 'bounce', event.reason);
                break;

            case 'spam_report':
                console.log(`SPAM REPORT: ${event.email}`);
                suppressEmail(event.email, 'spam_report');
                break;

            case 'delivered':
                console.log(`DELIVERED: ${event.email}`);
                logDelivery(event.email, event.timestamp);
                break;

            case 'open':
                console.log(`OPENED: ${event.email}`);
                trackEngagement(event.email, 'open', event.timestamp);
                break;

            case 'click':
                console.log(`CLICKED: ${event.email} - ${event.url}`);
                trackEngagement(event.email, 'click', event.timestamp, event.url);
                break;

            case 'dropped':
                console.log(`DROPPED: ${event.email} - ${event.reason}`);
                break;

            case 'deferred':
                console.log(`DEFERRED: ${event.email} - ${event.reason}`);
                break;

            default:
                console.log(`UNKNOWN EVENT: ${event.event} for ${event.email}`);
        }
    }

    // Always return 200 to acknowledge receipt
    res.status(200).send('OK');
});

app.listen(3000, () => console.log('Webhook server running on port 3000'));

Python with Flask

A Flask endpoint that processes webhook events and stores bounce records in a database.

Python
from flask import Flask, request, jsonify
from datetime import datetime

app = Flask(__name__)

@app.route('/webhooks/sendgrid', methods=['POST'])
def handle_sendgrid_webhook():
    events = request.get_json()

    if not isinstance(events, list):
        return jsonify({'error': 'Invalid payload'}), 400

    for event in events:
        event_type = event.get('event')
        email = event.get('email')
        timestamp = datetime.fromtimestamp(event.get('timestamp', 0))

        if event_type == 'bounce':
            reason = event.get('reason', '')
            suppress_email(email, 'bounce', reason)
            print(f'BOUNCE: {email} at {timestamp} - {reason}')

        elif event_type == 'spam_report':
            suppress_email(email, 'spam_report')
            print(f'SPAM REPORT: {email} at {timestamp}')

        elif event_type == 'delivered':
            log_delivery(email, timestamp)

        elif event_type in ('open', 'click'):
            url = event.get('url', '')
            track_engagement(email, event_type, timestamp, url)

        elif event_type == 'dropped':
            print(f'DROPPED: {email} - {event.get("reason", "")}')

    return '', 200

if __name__ == '__main__':
    app.run(port=3000)

PHP

A standalone PHP script that receives webhook events and writes bounce data to a log file.

PHP
<?php
$payload = file_get_contents('php://input');
$events = json_decode($payload, true);

if (!is_array($events)) {
    http_response_code(400);
    echo 'Invalid payload';
    exit;
}

foreach ($events as $event) {
    $type = $event['event'] ?? '';
    $email = $event['email'] ?? '';
    $timestamp = date('Y-m-d H:i:s', $event['timestamp'] ?? 0);

    switch ($type) {
        case 'bounce':
            $reason = $event['reason'] ?? '';
            // Add to suppression table
            suppressEmail($email, 'bounce', $reason);
            error_log("BOUNCE: {$email} at {$timestamp} - {$reason}");
            break;

        case 'spam_report':
            suppressEmail($email, 'spam_report');
            error_log("SPAM REPORT: {$email} at {$timestamp}");
            break;

        case 'delivered':
            logDelivery($email, $timestamp);
            break;

        case 'open':
        case 'click':
            $url = $event['url'] ?? '';
            trackEngagement($email, $type, $timestamp, $url);
            break;
    }
}

http_response_code(200);
echo 'OK';
?>

6. Security Considerations

Webhook endpoints are publicly accessible URLs, so it is critical to implement security measures to prevent unauthorized access and data tampering.

Verify the Signature

SendGrid signs every webhook payload using a verification key. Enable Signed Event Webhook in your SendGrid settings to receive an X-Twilio-Email-Event-Webhook-Signature header with each request. Verify this signature on your server before processing the payload to confirm it came from SendGrid and was not tampered with.

Use HTTPS Only

Always serve your webhook endpoint over HTTPS with a valid SSL certificate. This encrypts the payload in transit and prevents man-in-the-middle attacks. SendGrid will not deliver events to plain HTTP endpoints.

Validate the Payload

Always validate that the request body is a JSON array and that each event object contains the expected fields (email, event, timestamp). Reject malformed payloads with a 400 status code. Never trust user-supplied data -- sanitize email addresses and event fields before writing them to your database.

Implement Idempotency

SendGrid may deliver the same event more than once (for example, if your server returned a timeout before SendGrid received the 200 response). Use the sg_event_id field to deduplicate events. Store processed event IDs and skip any event you have already handled.

Signature Verification Example

Node.js
const crypto = require('crypto');

function verifySignature(publicKey, payload, signature, timestamp) {
    const timestampPayload = timestamp + payload;
    const decodedSignature = Buffer.from(signature, 'base64');

    const verifier = crypto.createVerify('SHA256');
    verifier.update(timestampPayload);

    return verifier.verify(publicKey, decodedSignature);
}

// In your webhook handler:
app.post('/webhooks/sendgrid', (req, res) => {
    const signature = req.headers['x-twilio-email-event-webhook-signature'];
    const timestamp = req.headers['x-twilio-email-event-webhook-timestamp'];
    const payload = JSON.stringify(req.body);

    if (!verifySignature(SENDGRID_PUBLIC_KEY, payload, signature, timestamp)) {
        return res.status(403).send('Invalid signature');
    }

    // Process events...
    res.status(200).send('OK');
});
  • Rate limit your endpoint -- Add rate limiting to your webhook endpoint to protect against abuse. Even with signature verification, rate limiting provides an additional layer of defense.
  • Process asynchronously -- Accept the webhook payload quickly (return 200 immediately), then process the events in a background queue. This prevents timeouts on large batches and ensures you do not miss events.
  • Monitor your endpoint health -- Set up uptime monitoring for your webhook URL. If your endpoint goes down, SendGrid will retry but eventually stop delivering events. Monitor for 4xx and 5xx error rates in your server logs.

Start Processing Email Events

Set up webhooks to automatically handle bounces, track engagement, and keep your email lists clean in real time.

Related Guides

Continue improving your email deliverability

API Integration Guide

Complete developer guide with code examples for JavaScript, Python, PHP, and cURL.

Read the guide →

Domain Setup Guide

Configure SPF, DKIM, and DMARC to authenticate your sending domain and improve deliverability.

Read the guide →

Email Bounce Rate Guide

Understand hard vs. soft bounces, industry benchmarks, and how to reduce your bounce rate.

Read the guide →