配信の挙動
プロバイダーの公式ドキュメントから読み取っています(最終確認 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 なのは、Twilio が SendGrid を所有しているからです。方式は SendGrid 独自のもので、Twilio の HMAC-SHA1 とは何も共通していません。ヘッダーのプレフィックスだけで判定する検証コードは、誤ったアルゴリズムを選びます。
あわせて読む
AnyHook は、SendGrid から受信するエンドポイントの手前に入ります。約 1 秒で応答し、サーバーがダウンしていれば独自のスケジュールでリトライします。すべてのイベントは再送できる状態で保存されます。 変えるのは URL 1 つだけで、コードはそのままです。