SendGrid 如何投递 Webhook

SendGrid 用 ECDSA P-256 签名,不是 HMAC,签名放在 x-twilio-email 系列请求头中。附核实过的格式和 Web Crypto 验证器。

投递行为

取自该平台的官方文档,最近核对于 2026-08-06。平台没有给出数字的地方,这张表也不填。

响应时限平台未公布
重试平台未公布
放弃平台未公布

签名

用一个经测试套件证明的可运行验证器核对,不是照着文档读来的。最近核对于 2026-08-20。

算法ECDSA P-256
待签名串{timestamp}{body}
编码base64
请求头x-twilio-email-event-webhook-signature
时间容差无强制要求

验证签名

要用原始请求体,逐字节一致,并且在任何 JSON 解析之前。框架一旦重新序列化请求体,本页的每一种方案都会失效。

// ECDSA P-256 over "{timestamp}{body}", signature and key both base64.
const b64 = (s) => Uint8Array.from(atob(s), (c) => c.charCodeAt(0));

const key = await crypto.subtle.importKey(
  "spki",
  b64(verificationKey), // Mail Settings → Signed Event Webhook
  { name: "ECDSA", namedCurve: "P-256" },
  false,
  ["verify"],
);
const ts = req.headers["x-twilio-email-event-webhook-timestamp"];
const valid = await crypto.subtle.verify(
  { name: "ECDSA", hash: "SHA-256" },
  key,
  b64(req.headers["x-twilio-email-event-webhook-signature"]),
  new TextEncoder().encode(ts + rawBody),
);

容易踩的坑

请求头写着 x-twilio-email,是因为 SendGrid 属于 Twilio;但这套方案是 SendGrid 自己的,和 Twilio 的 HMAC-SHA1 毫无关系。只按请求头前缀判断的验证器会选错算法。

延伸阅读

AnyHook 挡在接收 SendGrid Webhook 的端点前面:约 1 秒应答,你的服务器宕机时按自己的计划重试,并且每个事件都可以重放。 只改一个 URL,代码不用动。

工作原理 →