SendGrid webhooks: Event Webhook, signed events and Inbound Parse

To test the SendGrid Event Webhook, set its Post URL to a free webhooks.sh bin (no signup, 48 hours, 100 requests a day) and click Test Your Integration to see a JSON array of events live. Free bins accept bodies up to 100KB, so big event batches and emails with attachments can get a 413.

SendGrid has two webhooks: the Event Webhook posts JSON arrays of delivery and engagement events for mail you send, and Inbound Parse posts received emails as multipart/form-data. A free webhooks.sh bin shows either one live in your browser as soon as SendGrid sends it. Free bins accept request bodies up to 100KB, which fits test events and plain emails, though big event batches and emails with larger attachments won't fit.

Event Webhook or Inbound Parse?

You can do a SendGrid webhook test for either feature with a free bin as long as each request stays under 100KB.

FeatureEvent WebhookInbound Parse
SendsEvents for email you sendEmail received at your domain
FormatJSON array of eventsmultipart/form-data
SizeBatches up to 768 KBMessages up to 30MB with attachments
RetriesNon-2xx retried for up to 24 hours5XX retried, dropped after 3 days
Free bin fitTest batches fit; over 100KB gets a 413Plain emails fit; over 100KB gets a 413

Send a test batch to a free bin

  1. Create a free bin in your browser at view.webhooks.page or run curl -X POST https://webhooks.sh/v1/bins to get your ingest_url and view_url.
  2. In SendGrid, open Settings > Mail Settings, click Event Webhooks under Webhook Settings, and click Create new webhook.
  3. Paste the bin's ingest URL (https://<bin>.webhooks.page/) into Post URL and select the actions to be posted, such as delivered, bounce, open or click.
  4. Click Test Your Integration, watch the JSON array of example events arrive in the bin viewer, and then click Save.

SendGrid's docs note that webhook testing sites are handy for a quick configuration check and should only be used with the sample data from Test Your Integration, not real mail data.

Terminal

# Create a free bin (or open view.webhooks.page)
curl -X POST https://webhooks.sh/v1/bins

# Post URL for the Event Webhook, e.g.
# https://<bin>.webhooks.page/sendgrid

JSON

[
  {
    "email": "ada@example.com",
    "timestamp": 1793000000,
    "event": "delivered",
    "sg_event_id": "<sg_event_id>",
    "sg_message_id": "<sg_message_id>",
    "smtp-id": "<smtp-id>",
    "category": ["welcome"]
  }
]

Verify the Signed Event Webhook

To set up the SendGrid Signed Event Webhook, turn on Enable Signed Event Webhook under Security features and click Save. SendGrid generates an ECDSA key pair and shows the public verification key only once the webhook is saved, so clicking Test Integration before you hit Save doesn't test signature verification.

Each signed post carries a base64 signature in X-Twilio-Email-Event-Webhook-Signature and a timestamp in X-Twilio-Email-Event-Webhook-Timestamp. It's ECDSA with a public key, not an HMAC with a shared secret. To verify it, take the X-Twilio-Email-Event-Webhook-Timestamp value followed by the raw body bytes, hash them with SHA-256, and check the signature against SendGrid's public key, or use the Event Webhook helper in SendGrid's official libraries for C#, Go, Java, Node.js, PHP, Python and Ruby.

Both signature headers stay visible in the bin viewer. If you enable OAuth 2.0, SendGrid sends the access token in an Authorization header, which the viewer and API show as redacted because its name matches authorization.

import { createPublicKey, createVerify } from "node:crypto";

export function isFromSendGrid(publicKey, rawBody, signature, ts) {
  const key = createPublicKey({
    key: Buffer.from(publicKey, "base64"),
    format: "der",
    type: "spki",
  });
  const verifier = createVerify("sha256");
  verifier.update(ts);
  verifier.update(rawBody);
  return verifier.verify(key, signature, "base64");
}

Batches, retries and the 100KB limit

SendGrid posts events within about 30 seconds or when a batch reaches 768 KB. That limit is per server, so busy accounts can receive tens or hundreds of posts per second, and because duplicate events happen, you should deduplicate on sg_event_id (a string up to 100 characters). When your endpoint doesn't return a 2xx, a SendGrid webhook retry happens at increasing intervals for a rolling 24 hours after each failing event.

A free bin returns 413 (body_too_large) when a body is over 100KB, and returns 429 (daily_cap_reached) after 100 requests in a UTC day. Production batches over 100KB bounce off a free bin with a 413 and get retried, and 100 requests a day runs out fast at volume, so keep free bins for Test Your Integration and low-volume tests. Solo endpoints take bodies up to 5MB, and our webhook retries guide covers how retries behave.

Test Inbound Parse with a free bin

  1. Add an MX record for a dedicated hostname (such as parse under your domain) pointing to mx.sendgrid.net with priority 10.
  2. In SendGrid, go to Settings > Inbound Parse, click Add Host & URL, pick your receiving domain (it must be an authenticated domain), and paste your bin's ingest URL as the destination URL.
  3. Send an email to any address at that hostname except abuse, postmaster or unsubscribe.
  4. Inspect the multipart/form-data fields in the bin viewer, including from, to, subject, text, html, envelope, SPF, spam_score and attachment-info.

If you check POST the raw, full MIME message, the full message (headers, body and attachments) arrives in an email field alongside fields like to, from and SPF. Small plain text emails fit comfortably under a free bin's 100KB cap, while attachments will often push the total request size over 100KB and fail with a 413 (SendGrid's own message limit is 30MB). Inbound Parse does not follow redirects, only retries requests that return a 5XX response, and drops messages without notice if they can't be delivered after 3 days.

HOST    TYPE  TTL   SERVER
parse.  MX    3600  mx.sendgrid.net.

Keep the URL or move to localhost

You can claim a free bin before its 48-hour timer runs out with a free Google sign-in to keep the same .webhooks.page URL and get 30 days of rolling history, while limits stay at 100 requests per day and 100KB bodies. For larger payloads or local webhook testing, Solo ($12 a month) gives you a persistent endpoint on webhooks.host with 250,000 requests per billing period and bodies up to 5MB, which easily fits a 768 KB Event Webhook batch, plus the wsh CLI (wsh listen <endpoint> --to localhost:3000) to relay live deliveries to your local server. Inbound Parse emails over 5MB are still too big for Solo.

FAQ

How do I test the SendGrid Event Webhook?

Paste a free bin's ingest URL into the Post URL field in Settings > Mail Settings > Event Webhooks and click Test Your Integration. SendGrid POSTs a JSON array of sample events to the bin so you can inspect the headers and body without sending real mail.

Why does SendGrid send the same event twice?

SendGrid's docs say duplicate events can show up in the data the Event Webhook posts. Deduplicate incoming events using the sg_event_id property, which is a string of up to 100 characters.

How long does SendGrid retry a failed webhook?

For the Event Webhook, SendGrid retries any non-2xx response at increasing intervals for up to 24 hours after each event. For Inbound Parse, SendGrid retries only 5XX responses and drops the message without notice if it still can't be delivered after 3 days.

How do I verify SendGrid webhook signatures?

Turn on Enable Signed Event Webhook in your webhook's Security features and click Save to get the ECDSA public verification key. For each request, concatenate the X-Twilio-Email-Event-Webhook-Timestamp header and the raw body bytes, hash them with SHA-256, and verify the base64 signature in X-Twilio-Email-Event-Webhook-Signature using the public key or SendGrid's library helpers.

What format does SendGrid Inbound Parse post?

Inbound Parse posts multipart/form-data with parsed fields such as from, to, subject, text, html, headers, envelope, SPF and attachment-info. If you select POST the raw, full MIME message, the full MIME message arrives in an email field in place of the parsed headers, text, html and attachment fields.

Can a free bin receive Inbound Parse emails with attachments?

Only if the entire multipart/form-data request body stays under 100KB. Requests over 100KB are rejected by a free bin with a 413 (body_too_large), whereas Solo endpoints accept bodies up to 5MB.

Last updated

Try it in 10 seconds.

Create a free bin at view.webhooks.page — no signup, no install. Point your sender at the URL and watch requests land live. Bins expire after 48 hours.