デバッグ2026年4月18日7 分で読めます

Webhook が本番環境で失敗するとき:週末をつぶさずにデバッグする

Webhook の配信をデバッグする手順です。連携が動かなくなったときに、ヘッダー、ステータスコード、所要時間、ペイロードで何を確認すべきかを説明します。

プロバイダーのダッシュボードに残った記録プロバイダー配信ログ2xx届いたが、応答後の処理で失われた4xx受信側で拒否:署名、WAF、URL の誤り5xxその時刻にハンドラーが例外を投げたタイムアウトハンドラーが遅い。処理を非同期に移す
まずはプロバイダー自身の配信ログから。記録されたステータスを見れば、次にどの層を開けばよいかがわかり、30 分ほどの当て推量を省ける。

顧客からメールが届きます:「Stripe の Webhook が壊れている」。Stripe のダッシュボードを確認すると、イベントは発火しています。サーバーのログを確認しても、何もありません。Sentry にも何もありません。イベントはどこかに向かっています。ただ、期待している場所ではないだけです。

この記事は、よく起きる障害のパターンをもとにした、本番環境で Webhook の配信の問題をデバッグするための段階的な手順です。

TL;DR

  • まずはプロバイダーのダッシュボードから:イベントは発火しているか、どのステータスが返っているか?
  • 4xx は、こちらが拒否しているということです(署名、ミドルウェア、URL の間違い)
  • 5xx は、ハンドラーがクラッシュしたということです。そのタイムスタンプちょうどのエラー監視を確認します
  • タイムアウトは、ハンドラーが遅すぎるということです。重い処理は非同期に移し、200 をすばやく返します
  • 「デバッグに 2 時間かかった」を解決するのは、受信、送信、リトライ、再送を 1 か所でまとめて見られることです。たいていは Webhook 中継サービスを意味します

判断ツリー

ここから始めてください。30 分の当て推量を省けます。

Q1:プロバイダーは本当にイベントを送っているか?

プロバイダーのダッシュボードを確認します。Stripe なら Developers → Events(開発者 → イベント)、GitHub なら Settings → Webhooks → Recent Deliveries(最近の配信)、Shopify なら Dev Dashboard → Apps → 自分のアプリ → Logs です。

イベントがなければ、トリガーが発火していません。イベントを生成するはずの上流の状態をデバッグしてください。イベントがあれば、Q2 に進みます。

Q2:プロバイダーは 2xx を受け取っているか?

同じダッシュボードで、エンドポイントからのレスポンスのステータスを見ます。

  • 2xx:プロバイダーは成功したと考えています。アプリがそのイベントを一度も見ていないなら、イベントは届いたものの、レスポンスの後に失われています。非同期キュー、DB への書き込み、ハンドラーのロジックを確認してください。
  • 4xx:こちらが拒否しています。いちばん多いのは、署名検証の失敗、ヘッダーの欠落、またはミドルウェア(WAF、レートリミッター、認証レイヤー)によるリクエストのブロックです。
  • 5xx:エンドポイントがクラッシュしています。エラー監視を確認してください。「直近 1 時間」ではなく、この配信のタイムスタンプちょうどを見ます。
  • タイムアウト:エンドポイントが遅すぎます。Q4 を参照してください。
  • 接続拒否や DNS のエラー:エンドポイントに到達できません。これは別の問題で、ネットワーク、DNS、ファイアウォールのどこかにあります。

Q3:4xx なら、どれか?

  • 400:不正なリクエストです。たいていはヘッダーの不一致か、署名チェックが明確なエラーを返す前に例外を投げています。ボディの形式が崩れているときや署名がないときに、ハンドラーが何を返すかを確認してください。
  • 401/403:リクエストを認可されていないものとして拒否しています。署名検証が失敗していませんか?セッションが不要なはずのエンドポイントで、ミドルウェアがユーザーセッションを確認していませんか?
  • 404:URL が間違っています。Webhook のエンドポイントを移動するようなルート変更をデプロイしていませんか?
  • 413:ペイロードが大きすぎます。body-parser の上限を引き上げてください。
  • 429:レートリミッターが作動しています。Webhook のルートにレート制限をかけているものは、何であれ外すべきです。ほとんどの Webhook プロバイダーは限られた IP から送ってくるので、リミッターがそれらをまとめて 1 人の攻撃者として扱っているのかもしれません。

Q4:タイムアウトなら、何が遅いのか?

GitHub の猶予は 10 秒です。Shopify は 5 秒、Slack と Discord は 3 秒です。Stripe は数字を公開しておらず、ドキュメントには、時間のかかる処理の前に 2xx を返すようにとしか書かれていません。行ごとに出典を付けた完全な表は、Webhook プロバイダー別リファレンスにあります。

waitUntil · その 200 は守れない約束検証200 を返すwaitUntil(work)メモリ上のみ回収 → 消失。二度とリトライされない先に永続化 · その 200 は本当検証永続化200 を返す処理リトライ可能
実際の処理がどれだけ遅くても、どのプロバイダーのタイムアウトにも収まる形。

タイムアウトのほとんどは、ハンドラーの中で処理をしすぎることから起きます。よくある原因は、飽和したコネクションプールへの同期的なデータベース書き込み、ハンドラーの中からの別サービス(Stripe、Salesforce、Notion、自前の API)の呼び出し、メールや Slack メッセージの同期的な送信、そしてバックプレッシャーがかかっているログ収集基盤に向けた、ボディ全体の stdout への出力です。

直し方のパターン:署名を検証し、イベントをキューに入れ、200 を返します。ビジネスロジックはすべて、下流で非同期に行います。

実際によく壊れているところ

何年かこれを見てきた結果、80/20 で言うとこうなります:

  1. 署名検証の前にボディを再シリアライズしています。署名検証ガイドを参照してください
  2. プロキシや WAF が、ハンドラーに届く前に Webhook を握りつぶしています。Cloudflare、Vercel など、使っているプラットフォームのリクエストログを確認してください
  3. レート制限が作動しています。Cloudflare の Bot Management は、正当な Webhook を誤検知することがあります
  4. デプロイでエンドポイントの URL が変わったのに、誰もプロバイダー側を更新していません
  5. 本番環境で環境変数(署名シークレット、DB の URL)が欠けていて、ハンドラーが黙って 500 を返しています
  6. Webhook が 200 を返した後で、バックグラウンドジョブがクラッシュしています。プロバイダーは問題ないと思っていますが、何も処理されていません

6 番目はサーバーレスでは日常茶飯事です。レスポンスの後に予定した処理には、永続性の保証がまったくないからです:「Vercel で Webhook を取りこぼさずに受信する」を参照してください。症状が取りこぼしではなく重複なら、それはたいてい設計どおりの動作です:同じ Webhook を 2 回受け取ったときの記事を参照してください。

本当に役立つツール

プロバイダーのダッシュボードは、イベントを 1 件ずつ見るには便利ですが、パターンを見るには向きません。「直近 24 時間に失敗したすべてのイベント」を見たければ、手作業でスクロールすることになります。

ngrok と webhook.site はローカル開発用です。本番環境では役に立ちません。

自分のログは、正しいものを記録していれば役に立ちます。署名チェックの前に「webhook received for event evt_xxx」と記録していれば、少なくとも届いたことはわかります。ほとんどの人は後に記録するので、署名検証に失敗したイベントは見えなくなります。

AnyHook のような Webhook 中継サービスはハンドラーの前に置かれ、ヘッダー、ボディ、所要時間、レスポンスのステータス、試行回数を記録します。デバッグの流れは、イベントの一覧を見て、失敗しているものをクリックし、生のペイロードを確認して再送する、というものになります。

AnyHook でのデバッグの流れ

ユーザーから AnyHook のサポートに「Webhook が動かない」というメールが来たとき、ダッシュボードを見れば、たいてい 1 分もかからずに原因がはっきりします:

  1. イベントの一覧を開き、status=failed で絞り込みます
  2. 失敗しているイベントをクリックします
  3. 受信したヘッダー(署名はあったか?)、受信したボディ(期待した形だったか?)、転送先からのレスポンス(サーバーは何と返したか?)、所要時間の内訳(エッジ → キュー → 配信)を確認します
  4. まずいデプロイや不安定なネットワークによる一度きりの失敗なら、Replay(再送)をクリックして、成功するのを確認します
  5. すべてのイベントが失敗しているなら、何を直すべきかはもう正確にわかっています。直している間も、イベントは保存されたままで、プランの再送可能期間(Free は 3 日、Pro は 30 日、Scale は 90 日)のあいだは再送できます

2 時間のデバッグを 2 分に変えるのは、プロバイダーが実際に送ったリクエスト、サーバーが実際に返したレスポンス、その間のタイムスタンプが、すべて 1 か所にそろっていることです。それがなければ、3 つのダッシュボードと自分のログから組み立て直すことになります。

何かに登録する前に試すなら、アカウントなしで、コマンド 1 つで実際に使える受信 URL が返ってきます:

curl -X POST https://anyhook.net/api/v1/quickstart

レスポンスには、プロバイダーに設定する inbound_url、API 経由でイベントログを読むための api_key、そして claim_url が含まれます。このエンドポイントは一時的なものです:紐付け用の URL を開いてログインし、アカウントに紐付けない限り、エンドポイントとそのイベントは 7 日後に削除されます。紐付ければ同じ URL をそのまま使えます。

まとめ

日曜の午後がこれでつぶれることが思っている以上に多いなら、AnyHook を使えば、そのほとんどがダッシュボードでのクリックで済みます。

すべての記事2026年4月18日 · 7 分

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

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