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.

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.
| Topic | SendGrid | Sume |
|---|---|---|
| Algorithm | ECDSA | HMAC SHA256 |
| Key material | Public key from SendGrid settings | Shared secret from GET /v1/webhooks/signing-secret |
| Signature header | X-Twilio-Email-Event-Webhook-Signature | x-sume-webhook-signature: sume-v1=<hex> |
| Timestamp header | X-Twilio-Email-Event-Webhook-Timestamp | x-sume-webhook-timestamp |
| Signed input | Timestamp plus raw payload, hashed with sha256 | timestamp.raw_body |
| Rotation | See the Twilio page | Comma-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
- Smartsheet webhook verification challenge, then a Sume Format run
Smartsheet checks your endpoint with a challenge before a webhook is enabled. Answer it, then use change events to start Sume Format runs with a stable key.
- ClickUp taskStatusUpdated webhook: start a Sume Format run
Start a Sume Format run when a ClickUp task changes status: verify X-Signature, derive the Idempotency-Key from the task, cap spend, and write the result back.
- Trello webhook HMAC-SHA1 vs Sume HMAC-SHA256 in one receiver
Can one server verify Trello and Sume webhooks? Yes, with two verifiers: Trello signs body plus callback URL with SHA-1, Sume signs timestamp.body with SHA-256.
- How to add an MCP server to ChatGPT with developer mode
Turn on ChatGPT developer mode, create an app for the server's URL, and sign in with OAuth. The steps, with Sume's hosted MCP server as the example.
Written by Sume