AIエージェントを起動するwebhookで、何を守るべきですか?
守る対象はプロンプトでなく受信境界です。署名確認から権限の再確認まで、検査を分けて受けます。
Slackの投稿、GitHubの更新、決済完了をきっかけにAIエージェントを動かす場合を考えます。外部イベントが、要約、返信、台帳更新へ直結します。受信URLへ偽のイベントを送られれば、モデルへ命令を渡す前に業務操作が始まります。
webhookは、外部サービスがHTTPでイベントを通知する仕組みです。安全な受信は、次の順に検査を分けます。
- 署名を確認する
- 古い通知を拒む
- 同じ通知を二度適用しない
- 現在の権限を確認する
一つの検査で全てを保証しようとしません。
webhookの受信は、どんな手順で作りますか?
生本文の署名検証、古い通知の拒否、受信台帳と実行依頼の確定、業務の冪等性、権限の再確認の順に作ります。

設計例は、同じ案件更新イベントを2回受けても、同じ案件の要約が1件だけ確定する流れです。署名は受信本文を検査し、受信台帳は通知の重複を検査します。処理途中で止まる場合に備え、要約の保存側も同じイベントIDを使います。
- JSONへ変換する前の本文で署名を検証します。GitHubは
X-Hub-Signature-256を使います。秘密鍵と受信本文からHMAC-SHA256を計算して比較する手順を示しています[1]。Slackも、JSONへ変換する前の生の本文を使うよう案内しています[2]。生の本文、署名ヘッダー、受信時刻を、最初に確保します。秘密はコードやログへ出さず、秘密管理サービスから読み込みます。比較には、定時間の比較処理(処理時間から一致箇所を推測されにくい方式)を使います。検証に失敗したイベントは、モデルにも業務キューにも渡しません。 - 送信元ごとに検証器を作ります。Stripe、Twilio、Shopifyも署名検証を案内しています。ただし、ヘッダー名、署名対象、複数署名の扱いは異なります[3][4][5]。公式ライブラリがある場合は、優先します。Cloudflare Agentsの文書も、解析前に個別検証する構成を示しています[6]。提供元ごとに方式が違うためです。
- 古い通知と重複を拒みます。過去の正規イベントは、そのまま再送されると署名検証を通ります。Slackは、タイムスタンプを署名対象に含めています。現在時刻との差を見て、再送攻撃を避ける例です[2]。時刻窓は一律に決めず、送信元の再送仕様と自社の遅延許容時間に合わせます。サーバー時刻のずれも監視します。タイムスタンプを署名しない提供元では、イベントIDや配信IDを保存します。受信台帳には、
送信元、イベントIDの一意制約を置きます。台帳の保持期間は、提供元の再送期間より短くしません。 - 受信台帳と実行依頼を同じ確定処理にします。ここからは、一次資料の要約ではありません。受信台帳と実行依頼の間でイベントを失わないための、編集部の設計提案です。署名検証後すぐに業務処理を呼ぶと、台帳の保存だけ成功して実行依頼が消える失敗が起きます。逆に、実行依頼だけ成功して台帳が残らない失敗も起きます。台帳への保存と、実行待ちキューへ渡す依頼を、同じデータベース処理で確定します。台帳には、イベントID、送信元、受信時刻、本文ハッシュ、状態を保存します。送信予定も台帳へ先に保存するoutbox(送信予定表)方式を使います。別の送信担当が未送信行をキューへ届け、送信済み時刻を記録します。
- 状態を分けて再送に備えます。状態は、「受信」「署名確認済み」「実行待ち」「実行中」「完了」「再試行待ち」「恒久失敗」に分けます。同じIDの再送を受けたとき、完了済みなら保存済みの結果を返します。実行中なら、新しい処理を作りません。失敗時は、試行回数、次回時刻、最後のエラー分類を更新します。通信障害だけを再試行し、権限不足や入力不備は無限に繰り返しません。
- 業務側にも冪等性を持たせます。冪等性は、同じ処理を繰り返しても結果が一つになる性質です。業務処理側にもイベントIDを渡します。顧客台帳の更新、メッセージ送信、請求処理ごとに、一度だけ確定した記録を残します。注文IDや案件IDを冪等キーにし、「未作成なら作る」を一つの取引として扱います。
- 署名後にも認可をやり直します。署名後に、対象の組織、チャンネル、リポジトリ、顧客IDが許可一覧にあるかを確認します。AIエージェントが返信や更新を行う直前には、現在の権限を照合します。受信時の権限は使いません。
秘密の更新も、停止なしで行えるようにします。新旧二つの秘密を短期間だけ検証に使います。送信元の切替を確認したら、旧秘密を無効化します。
監視は、次の項目を別々に行います。検証失敗率、時刻窓超過、重複率、未知の送信元、業務側の冪等性違反です。本文や署名そのものをログへ残す場合は、個人情報と秘密の混入を避けます。
webhookの受信がうまくいかない例には、どんなものがありますか?
整形後の本文で署名する、署名だけで信用する、台帳だけで安心する、の3つが典型です。
- 整形し直した文字列で署名を計算する。受信したJSONを解析し、整形し直すと、空白や文字コード、キー順が変わります。送信側が署名したバイト列と一致しません。
- 共通の「署名文字列」へ正規化する。提供元ごとに、ヘッダー名や署名対象が違います。送信元ごとの検証器が必要です。
- 署名が正しければ信用する。正しい署名は、想定した送信元から改ざんなく届いたことを確かめるだけです。イベント内容が現在も業務上許されるとは、保証しません。
- 受信台帳の重複排除だけで安心する。キューの再配信や処理途中の停止は残ります。たとえば、請求書を作成してから完了記録を書く間に停止すると、再実行で請求書が二つできます。重複排除は、厳密な一度だけの実行も保証しません。外部API呼び出しを伴う場合は、呼び出し先の冪等キー、実行記録、補償手順を組み合わせます。
- 署名検証の成功だけを合格にする。本番前に、次を試します。偽イベントを拒み、正規の再送には成功応答を返し、業務結果が一件だけ残るところまで確認します。
本番前に試す項目は、次のとおりです。
- 本文の1文字変更
- 古い正規イベントの再送
- 同じイベントの同時送信
- 台帳確定直後・実行待ち登録直前の停止
- 処理途中の停止
- 秘密更新中の新旧署名
定期点検では、受信済みのまま止まった件数、実行待ちの滞留時間、再試行回数、恒久失敗の未対応件数を確認します。
4つの検査は、それぞれ何を防ぎ、何が残りますか?
署名は偽イベント、時刻窓と台帳は再送、冪等性は二重処理、権限の再確認は不許可の操作を防ぎます。
| 検査 | 防ぐもの | 残る限界 |
|---|---|---|
| 生本文の署名検証 | 偽のイベント、改ざん | 過去の正規イベントの再送は通る |
| 時刻窓・イベントIDの台帳 | 古い通知、重複した通知 | 厳密な一度だけの実行は保証しない |
| 業務側の冪等性 | 途中停止後の再実行による二重処理 | 外部API呼び出しには、冪等キーや補償手順が別に必要 |
| 現在の権限の再確認 | 業務上許されない操作 | 署名の検証とは別に、許可一覧の管理が必要 |
よくある質問
JSONへ変換する前の、受信したままの生の本文です。解析して整形し直した文字列で計算すると、送信側が署名したバイト列と一致しません。空白や文字コード、キー順の違いが原因です。提供元ごとにヘッダーと方式が異なるため、検証器も送信元ごとに作ります。
信用しきれません。正しい署名は、想定した送信元から本文が改ざんされず届いたことを示すだけです。過去の正規イベントの再送も、検証を通ります。内容が現在も業務上許されることも保証しません。時刻窓、重複排除、現在の権限確認を別に行います。
重複排除は、厳密な一度だけの実行を保証しません。キューの再配信や処理途中の停止は残ります。業務側でも、注文IDなどの冪等キーで「未作成なら作る」を一つの取引として扱います。外部API呼出しには、実行記録や補償手順を組み合わせます。
次の一歩は何から始めればよいですか?
受信処理で生本文を確保し、受信台帳を作ることから始めます。次に送信元ごとの検証器を作ります。
- 受信処理で、生の本文、署名ヘッダー、受信時刻を確保します。
- 送信元ごとの検証器と、受信台帳の一意制約を作ります。
- 本番前に、再送と途中停止を試し、業務結果が一件だけ残ることを確認します。
出典(一次情報と確認範囲)
資料確認日:2026年9月29日。本文中の手順例は編集部の提案で、導入効果の実測値ではありません。

