SendGrid webhook signature verification failing? It's ECDSA, not HMAC

You enabled Signed Event Webhook Requests, copied the key from the dashboard, and your verification code never passes. The usual reason is structural: almost every webhook provider signs with HMAC-SHA256 and a shared secret, so that's what most verifier snippets assume — but SendGrid signs with ECDSA, an asymmetric algorithm. SendGrid keeps a private key, you get the public Verification Key, and no HMAC routine can ever reproduce the signature no matter which key you feed it.

What SendGrid actually sends

With signing enabled (Settings → Mail Settings → Signed Event Webhook Requests — SendGrid generates the key pair for you), every event POST carries two extra headers:

HeaderContents
X-Twilio-Email-Event-Webhook-Signaturebase64, DER-encoded ECDSA signature (a SEQUENCE of the two integers r, s)
X-Twilio-Email-Event-Webhook-Timestamptimestamp string that was prepended to the payload before signing

The signed content is timestamp + raw body — SHA-256 hashed, then signed with the private half of a P-256 (prime256v1 / secp256r1) key. Your Verification Key is the public half, shipped as base64-encoded DER (SPKI) — without PEM framing.

The failure causes, in the order to check them

  1. You're verifying HMAC. If your code calls an HMAC function, or you pasted the Verification Key into a generic "HMAC calculator", it can never match. Use an ECDSA verify: the official helpers (@sendgrid/eventwebhook for Node, sendgrid-go's helpers/eventwebhook, and equivalents in the other SendGrid SDKs) wrap this correctly.
  2. Wrong key material. The Verification Key is not your API key and not a "webhook secret" — it's the public key shown in the Signed Event Webhook dialog (also retrievable via the webhook API). Two sub-traps: OpenSSL and most PEM-based tooling won't load it until you wrap the base64 in -----BEGIN PUBLIC KEY----- / -----END PUBLIC KEY----- lines; and if you rotate the key in the dashboard, deliveries signed with the old key stop verifying immediately.
  3. The body isn't byte-exact. The signature covers the raw bytes. Frameworks that parse JSON and hand you the parsed object are the classic killer: re-serializing changes whitespace/key order/unicode escapes and verification fails. Verify against the raw request body, before any body-parser touches it.
  4. You forgot the timestamp. The hash input is timestamp + body, concatenated with no separator. Verifying the body alone fails even with the right key and perfect bytes.
  5. Signature-format mismatch in hand-rolled code. The header carries a DER signature. Node's crypto.verify accepts DER natively, but WebCrypto (crypto.subtle.verify) wants the raw 64-byte r||s (P1363) form — you must convert, or every valid signature reads as invalid.

Check a real delivery in one minute

Because the Verification Key is public, you can safely paste it into tools — there's nothing to leak. Two fast paths:

  1. In your browser: the signature debugger now speaks SendGrid ECDSA. Paste the Verification Key, the timestamp header, the raw body, and the signature header — it verifies entirely client-side via WebCrypto (including the DER→raw conversion) and tells you whether the delivery is genuine.
  2. On arrival: point the Event Webhook at a Hookden capture bin, choose SendGrid as the bin's signature scheme, and paste the Verification Key. Every delivery gets a ✓/✗ badge: ✓ means SendGrid's signature is valid and any failure is in your verifier's inputs (Causes 1, 3, 4, 5); ✗ means the key is wrong or the payload was altered in transit (Cause 2). The bin stores the body byte-exact, so you can copy the same bytes into the debugger or your test suite.

A useful corollary of signing timestamp + body (and not the URL): relayed and replayed deliveries keep verifying, because Hookden re-delivers the original bytes and headers unchanged.

Note the same ECDSA mechanism is available for the Inbound Parse webhook's signature verification — the checks above apply there too.

Related: Mailgun (the other email provider with a scheme HMAC testers can't handle), Stripe signature verification failed (timestamp-prefixed HMAC — what most snippets assume), and a realistic SendGrid event payload you can open in a live bin.

No signup needed. Or from your terminal: curl https://hookden.pages.dev/new

← All guides · Signature debugger · Payload examples · Docs · Hookden vs webhook.site