Twilio の Webhook 配信の仕組み

タイムアウトとリトライの設定項目はすべて変更でき、他の主要な送信元ではこれができません。HMAC-SHA1 の URL 署名と Node の検証コードを掲載しています。

配信の挙動

プロバイダーの公式ドキュメントから読み取っています(最終確認 2026-08-06)。プロバイダーが数値を示していない項目には、この表も数値を載せていません。

応答期限15 秒

設定可能。rt(読み取りタイムアウト)はデフォルト 15000ms(最大 15000)、ct(接続)はデフォルト 5000ms(最大 10000)、tt(合計)はデフォルト 15000ms

ハンドラーが期限内に応答するか確認する

リトライ設定可能:rc は 0〜5、デフォルトは 1。rp で対象とする失敗の種類を選ぶ
打ち切りプロバイダーは未公表
出典https://www.twilio.com/docs/usage/webhooks/webhooks-connection-overrides

署名

ドキュメントを読んだだけではなく、テストスイートで裏付けた動作する検証コードで確認しています。最終検証日は 2026-08-20 です。

アルゴリズムHMAC-SHA1
署名対象full URL + form params sorted by key
エンコードbase64
ヘッダーx-twilio-signature
許容範囲適用なし

検証する

JSON としてパースする前の生のリクエストボディを、バイト単位でそのまま使います。フレームワークがペイロードを再シリアライズした時点で、このページのどの方式も検証に失敗します。

const crypto = require("node:crypto");

// base64(HMAC-SHA1(auth token, url + form params sorted by key, concatenated))
const sorted = Object.keys(req.body).sort();
const base = fullUrl + sorted.map((k) => k + req.body[k]).join("");
const expected = crypto
  .createHmac("sha1", authToken)
  .update(base)
  .digest("base64");
const received = Buffer.from(req.headers["x-twilio-signature"] ?? "");
const wanted = Buffer.from(expected);
// timingSafeEqual throws on a length mismatch, so check that first.
const valid =
  received.length === wanted.length && crypto.timingSafeEqual(received, wanted);

署名は、クエリ文字列を含む公開 URL 全体を対象にしています。ハンドラーの前で TLS を終端したり、パスを書き換えるプロキシを置いたりすると、Twilio が署名した URL が変わります。その不一致は、偽造されたリクエストとまったく同じに見えます。

落とし穴

URL ごとに 5 つの配信パラメーター(接続タイムアウト、読み取りタイムアウト、合計時間、リトライ回数、リトライポリシー)を設定できます。それでもほぼ全員がデフォルト(合計 15 秒、リトライ 1 回)のまま使っています。つまみはあっても、誰も回しません。

あわせて読む

AnyHook は、Twilio から受信するエンドポイントの手前に入ります。約 1 秒で応答し、サーバーがダウンしていれば独自のスケジュールでリトライします。すべてのイベントは再送できる状態で保存されます。 変えるのは URL 1 つだけで、コードはそのままです。

仕組みを見る →