Webhook2026年8月6日7 分で読めます

Twilio の Webhook タイムアウト:ほぼ誰も回さないダイヤル

Twilio は、接続・読み取りタイムアウト、合計時間、リトライ回数、リトライポリシーを設定できる唯一の主要な送信元です。その 5 つのダイヤルを、誰も回していません。

Twilio ではこれを設定できる。設定する人はほとんどいない。ct · 接続既定 5000ms · 最大 10000ms · TCP 接続rt · 読み取り既定 15000ms · 最大 15000ms · 最初のバイトtt · 合計既定 15000ms · 最大 15000ms · すべての試行rc = リトライ回数、0〜5、既定 1 · rp = リトライ対象とする失敗の種類
接続、読み取り、合計。それぞれにデフォルト値と上限がある。上限を超える分に交渉の余地はない。

ほかの Webhook の送信元は、数字を示して、それに合わせることを求めます。Shopify は 5 秒、GitHub は 10 秒、Slack は 3 秒で、どれも Webhook プロバイダー別リファレンスにまとめています。文句は言えても、変えることはできません。

Twilio は例外です。タイムアウトを Webhook 自体のパラメーターとして公開していて、リトライ回数と、どの失敗をリトライするかのポリシーも設定できます。ダイヤルは 5 つあり、どれもデフォルト値付きで提供されていますが、ほとんどの連携では誰も触ったことがありません。

これはどちらの方向にとっても、もったいない話です。最もよくある 2 つのケースのどちらでも、デフォルト値が合っていないからです。

TL;DR

  • ct 接続タイムアウト:デフォルト 5000ms、範囲は 100〜10000
  • rt 読み取りタイムアウト:デフォルト 15000ms、範囲は 100〜15000
  • tt リトライを含む合計時間:デフォルト 15000ms、範囲は 100〜15000
  • rc リトライ回数:0〜5、デフォルト 1
  • rp リトライポリシー:どの種類の失敗をリトライするか
  • 上限は固定です。いくら払っても、15 秒を超える読み取りタイムアウトは手に入りません

タイムアウトは 1 つではなく 3 つ

多くの人は、Webhook のタイムアウトを「送信元がレスポンスをどれだけ待つか」という 1 つのイメージでとらえています。Twilio はそれを 3 つの段階に分けていて、どれに引っかかっているかがわかると、診断がまったく変わります。

ct は「Twilio がサーバーへの TCP 接続を確立するまで待つ時間(ミリ秒)」です。デフォルトは 5000ms、最大は 10000ms です。これに引っかかるのは、Twilio がソケットをまったく開けなかったということです。原因は DNS、TLS、ファイアウォール、あるいは完全にダウンしたオリジンです。ハンドラーが遅いせいでは決してありません。ハンドラーはまだ動いていないからです。

rt は「Twilio が Webhook リクエストを送った後、HTTP レスポンスのパケットをそれぞれ待つ時間(ミリ秒)」です。デフォルトは 15000ms、最大も 15000ms です。一般にタイムアウトと言うときに指しているのはこれで、ハンドラーが実際に使える時間です。

tt は「リトライを含む、すべてのタイムアウトに許される合計時間」です。デフォルトは 15000ms、最大も 15000ms です。一連の試行全体の上限で、多くの人がつまずくのはこのパラメーターです。リトライにも制約がかかるからです。

Twilio ではこれを設定できる。設定する人はほとんどいない。ct · 接続既定 5000ms · 最大 10000ms · TCP 接続rt · 読み取り既定 15000ms · 最大 15000ms · 最初のバイトtt · 合計既定 15000ms · 最大 15000ms · すべての試行rc = リトライ回数、0〜5、既定 1 · rp = リトライ対象とする失敗の種類

tt と rt は、デフォルト値も最大値も同じであることに注目してください。提供時のデフォルトのままだと、読み取りタイムアウト 1 回で合計時間をすべて使い切り、設定したつもりのリトライは実行される機会がありません。リトライに意味を持たせたいなら、rt を下げて、tt の中に 2 回以上の試行が収まるようにする必要があります。

この相互作用こそ、ここでいちばん理解しておくべきことです。パラメーターを個別に読んでいるだけでは気づきません。

リトライが実際に起きるように設定する

ハンドラーが普段 400ms で応答し、あきらめる前に 2 回リトライしたいとします。

rt = 3000     // 3s per attempt, generous against a 400ms handler
rc = 2        // two retries
tt = 12000    // room for three attempts plus connect overhead
ct = 3000     // fail fast if the socket will not open
rp = ct,rt,5xx

これで、応答しなくなったハンドラーは 15 秒ではなく 3 秒で見切られ、リトライは同じインシデントが続いているうちに届きます。デフォルトのままだと、応答しないリクエストが 1 件あるだけで 15 秒をすべて使い切り、設定したリトライは何の知らせもなく一度も実行されません。

リトライポリシーは意図して決める価値があります。4xx をリトライするのはたいてい間違いです。400 や 404 はリクエストそのものが受け付けられないという意味で、もう一度送っても同じ答えが返ってくるからです。リトライするのは、接続タイムアウト、読み取りタイムアウト、そして 5xx です。一時的である可能性が高いのはこれらの失敗です。これは「Webhook リトライの指数バックオフとジッター」と同じ考え方を、それを設定で表現できる送信元に当てはめたものです。

もう半分はフォールバック URL

Twilio のドキュメントは、「サーバーで障害が起きたときに、フォールバック URL へのフェイルオーバーを速めるために」タイムアウトを短く設定するよう勧めています。

フォールバック URL は、プライマリが失敗したときに Twilio が呼び出す別のエンドポイントです。主要な送信元が提供するものの中では、組み込みのサーキットブレーカーにいちばん近い機能です。ただし役に立つのは、発信者がまだ電話口にいるうちにそこへ到達できるほど、タイムアウトが短い場合だけです。決済の Webhook とは違い、音声やメッセージングで rt と ct が重要になるのはまさにこのためです。

フォールバック先には、プライマリと同じ理由では失敗しえないものを指定してください。同じホスト上にあり、同じロードバランサーの後ろにあり、同じデータベースを共有するフォールバックは、フォールバックではありません。オブジェクトストレージから配信する静的な TwiML なら、フォールバックになります。

ダイヤルでは解決できないケース

Twilio の設定では救えない種類のケースがあり、それがますます一般的になっています。

SMS が届き、その返信を LLM に書かせたいとします。その呼び出しには 20〜60 秒かかります。読み取りタイムアウトの上限は 15 秒で、引き上げることはできません。rt、tt、rc をどう組み合わせても、60 秒かかるハンドラーを最大 15 秒の中に収めることはできません。

答えは Slack の場合と同じです:受け取りの応答と、実際の返信を分けます。空の TwiML をすぐに返して Twilio の時計を止め、モデルの処理が終わったら Messages API で実際の返信を送ります。

export async function POST(req: Request) {
  const raw = await req.text();                     // raw body, for the signature
  if (!verifyTwilioSignature(req.url, raw, req.headers.get("x-twilio-signature"))) {
    return new Response("unauthorized", { status: 403 });
  }

  await queue.publish(raw);                         // durable, ~10ms

  // Empty TwiML: acknowledged, nothing to say yet
  return new Response('<?xml version="1.0" encoding="UTF-8"?><Response></Response>', {
    headers: { "content-type": "text/xml" },
  });
}

ワーカーは、回答ができた時点で messages.create を呼び出します。ユーザーには 1 分以内に返信が届きますが、テキストでのやり取りならそれが普通です。Twilio から見れば、40 ミリ秒でレスポンスが返っています。この議論をより広く扱ったのが AI エージェント向けの Webhook の記事です。

音声の場合は、実際の処理が進む間、メッセージを流すか発信者をキューに入れる TwiML を返すのが同じやり方です。無音を聞かされる発信者は、遅い SMS よりも悪い失敗だからです。

署名検証の方式が違う

X-Twilio-Signature は、ボディに対する単純な HMAC ではありません。Twilio は署名対象の文字列を、リクエストの完全な URL と、フォームエンコードのリクエストの場合はキーでソートして連結した POST パラメーターから組み立てます。それを auth token で HMAC-SHA1 にかけ、base64 でエンコードします。

実務上の影響が 2 つあります。

URL が署名の一部なので、URL を書き換えるものはすべて検証を壊します。ポートを取り除くプロキシ、スキームを https から http に変えるロードバランサー、ヘッダーから URL を組み立て直すフレームワークは、どれも正しく見えるのに検証に失敗するリクエストを生みます。本番環境で検証に失敗し、ローカルでは通るなら、コードが署名に使っている URL と、Twilio が呼び出した URL を正確に比べてください。

アルゴリズムは SHA256 ではなく SHA1 で、フォームエンコードのリクエストでは入力が生のリクエストボディではありません。Stripe 方式の検証コードを流用するのではなく、Twilio 自身の validateRequest ヘルパーを使ってください。組み立て方が本当に違うからです。プロバイダーごとの比較は「Stripe、GitHub、Shopify の Webhook 署名を検証する」にあります。

AnyHook の位置づけ

ダイヤルは便利ですが、固定の上限があります。Twilio が待つのは最大でも 15 秒で、正当なハンドラーでもそれ以上かかるものはたくさんあります。その筆頭が LLM の呼び出しです。タイムアウトチェッカーは、ハンドラーの所要時間をこの 15 秒と比べて計測します。各プラットフォームで先に応答する対処法は、「Webhook ハンドラーが LLM を呼ぶ」にあります。

Webhook の URL を in.anyhook.net/you/twilio に向ければ、その上限はもう自分の上限ではなくなります。AnyHook はエッジから約 1 秒で応答し、その後、プランに応じて、そのコンピュート枠の範囲内で 60〜300 秒の持ち時間でハンドラーに配信します。

  • エンドポイントへのリトライは指数バックオフで行われ、tt が許すよりはるかに長い期間続きます
  • すべてのイベントは配信前に永続化されるので、ハンドラーが失敗してもメッセージは失われず、再送すれば済みます
  • 1 回、5 回、20 回連続の失敗で通知し、20 回で自動的に一時停止します

レスポンスに TwiML が必要なら、それは自分のハンドラーで扱ってください。中継サービスが合うのは、Webhook が指示を求めるリクエストではなく、通知である場合です。

まとめ

Twilio は、ほかのどの主要な送信元よりも、Webhook のタイミングを細かく制御させてくれます。そしてデフォルト値は、すぐに応答するハンドラーを前提にしています。rt を下げてリトライが tt に収まるようにし、rp で 4xx をリトライしないようにし、フォールバック URL は障害の起こり方が異なるものに向けてください。

そのうえで、上限は上限として受け入れてください。最大は 15 秒で、ハンドラーに 1 分かかるなら、どんな設定でも届きません。先に応答し、返信は別の経路で送ります。

すべての記事2026年8月6日 · 7 分

Webhook を、もう取りこぼさない。

URL を 1 つ変えるだけで、リトライ、イベントログ、1 クリックでの再送が使えます。先に動きを見ることもできます。新規登録して 30 秒の障害訓練を実行すると、サーバーがダウンしていてもイベントが失われない様子を確認できます。サーバーを用意する必要はありません。