SendGrid ECDSA event webhook vs a Sume HMAC verifier: two checks

SendGrid signs event webhooks with ECDSA, while Sume uses HMAC SHA256. Here is why one verifier will not cover both and how to start a Sume run from an event.

4 min readSume
All posts

No, one verifier cannot check both. SendGrid signs its Event Webhook with ECDSA, which needs a public key, while Sume signs with HMAC SHA256, which needs a shared secret. If a SendGrid event should start a Sume Format run, the receiver verifies SendGrid's way first, then calls Sume with its own key.

The two schemes differ in more than the algorithm, so mixing helper code is a common source of bugs.

What SendGrid documents

Twilio's event webhook security page describes ECDSA signatures with two headers, X-Twilio-Email-Event-Webhook-Signature and X-Twilio-Email-Event-Webhook-Timestamp (read 2026-10-10). The check hashes the timestamp joined to the payload with sha256, and the page stresses using the raw bytes of the body.

For the exact key format and the verification calls in each language, follow that page and its libraries; this post does not restate them. What matters for Sume is the shape: a public key you fetch from your SendGrid settings, not a secret you share.

SendGrid Event Webhook (Twilio docs, read 2026-10-10) and Sume run webhook (docs.sume.com)
TopicSendGridSume
AlgorithmECDSAHMAC SHA256
Key materialPublic key from SendGrid settingsShared secret from GET /v1/webhooks/signing-secret
Signature headerX-Twilio-Email-Event-Webhook-Signaturex-sume-webhook-signature: sume-v1=<hex>
Timestamp headerX-Twilio-Email-Event-Webhook-Timestampx-sume-webhook-timestamp
Signed inputTimestamp plus raw payload, hashed with sha256timestamp.raw_body
RotationSee the Twilio pageComma-separated sume-v1= entries during rotation

Receiver order

Read the raw body once and keep the bytes. Verify SendGrid first and return 4xx on failure. Only then parse the events, which arrive as batches, and decide which are worth a run. A bounce or a spam report might start a Format that drafts a win-back clip; a plain delivery event should not.

Do not forward SendGrid's signature to Sume. The two systems never talk to each other. Your receiver is the only party that holds both trust relationships.

Create the Sume run

Build the Idempotency-Key from a SendGrid event identifier your payload carries, and a version suffix. Sume's key is scoped to one Format and may be up to 255 characters. A repeat returns 200 with idempotency_hit: true; a different body under the same key returns 409 idempotency_conflict; a duplicate that arrives while the first is still being created returns 409 idempotency_key_in_use.

Because events come in batches, create runs for the whole batch with the bulk endpoint instead of a loop: POST .../bulk-runs accepts 1 to 100 items at concurrency 1 to 16, and you poll GET /v1/format-run-queues/{id}. Queue status completed means every item is terminal, so branch on counts.failed. Bulk has no queue-level webhook, only a per-item webhook_url.

Verify Sume on the way back

The result reaches you as format.run.terminal with outcome of ok, degraded or error. The Sume verifier is short: HMAC SHA256 over timestamp.raw_body, a comparison against each sume-v1= entry in constant time, and a five-minute replay window. Reject an empty secret. Keep SendGrid's public-key check and this one in separate functions with separate tests.

Sume retries a refused delivery up to 10 times with exponential backoff capped at one hour, and each attempt has a 10 second timeout. POST /v1/format-runs/{id}/webhook/redeliver replays a terminal event when you were down.

Test both verifiers separately

Write one test per scheme with its own fixture. For SendGrid, use a payload and signature pair produced by Twilio's own library, so you are not testing your code against itself. For Sume, POST /v1/webhooks/test-deliveries sends a dummy signed webhook.test event to a URL you type, which proves your route, secret and clock handling without spending on a real run. It never replays a real run.

Run the two verifiers on separate routes if you can. A path such as one for inbound mail events and one for Sume results makes a misrouted request fail at the first check, and keeps a SendGrid secret rotation from touching the Sume secret. Both should refuse to start when their key material is missing.

Sources

Related posts

More in Integrations

All Integrations posts

Written by Sume