Slackの投稿、GitHubの更新、決済完了をきっかけにAIエージェントを動かすと、外部イベントが要約、返信、台帳更新へ直結する。受信URLへ偽のイベントを送られれば、モデルへ命令を渡す前に業務操作が始まる。ここで守る対象はプロンプトではなく、受信境界である。
webhookは、外部サービスがHTTPでイベントを通知する仕組みだ。安全な受信は「署名を確認する」「古い通知を拒む」「同じ通知を二度適用しない」「現在の権限を確認する」の順に分ける。一つの検査で全てを保証しようとしない。
JSONへ変換する前の本文を残す
最初の失敗は、受信した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の再送には、実行待ち登録済み以降だけ成功応答を返して処理を増やさず、受信済みなら未完了の登録を回復する。台帳の保持期間は、提供元の再送期間より短くしない。
重複排除と業務の冪等性を分ける
受信台帳で重複を止めても、キューの再配信や処理途中の停止は残る。たとえば「請求書を作成してから完了記録を書く」間に停止すると、再実行で請求書が二つできる。業務側でも、注文IDや案件IDを冪等キーにし、「未作成なら作る」を一つの取引として扱う。
Cloudflareの案内はイベントIDを使う重複処理の考え方を示す[6]。ただし、重複排除は厳密な一度だけの実行を保証しない。外部API呼び出しを伴う場合は、呼び出し先の冪等キー、実行記録、補償手順を組み合わせる。
署名後にも認可をやり直す
正しい署名は、想定した送信元から本文が改ざんされず届いたことを確かめる。イベント内容が現在も業務上許されるとは保証しない。署名後に、対象の組織、チャンネル、リポジトリ、顧客IDが許可一覧にあるか確認する。AIエージェントが返信や更新を行う直前には、受信時の権限ではなく現在の権限を照合する。
秘密の更新も停止なしで行えるようにする。新旧二つの秘密を短期間だけ検証に使い、送信元の切替を確認したら旧秘密を無効化する。検証失敗率、時刻窓超過、重複率、未知の送信元、業務側の冪等性違反を別々に監視する。本文や署名そのものをログへ残す場合は、個人情報と秘密の混入を避ける。
本番前には、本文の1文字変更、古い正規イベントの再送、同じイベントの同時送信、台帳確定直後・実行待ち登録直前の停止、処理途中の停止、秘密更新中の新旧署名を試す。署名検証の成功だけを合格にしない。偽イベントを拒み、正規の再送には成功応答を返し、業務結果が一件だけ残るところまで確認する。
受信台帳と実行依頼を同じ確定処理にする
ここからは、一次資料の要約ではなく、受信台帳と実行依頼の間でイベントを失わないための編集部の設計提案である。署名検証後すぐに業務処理を呼び出すと、受信台帳の保存だけ成功して実行依頼が消える、または実行依頼だけ成功して台帳が残らない失敗が起きる。受信台帳へイベントID、送信元、受信時刻、本文ハッシュ、状態を保存する処理と、実行待ちキューへ渡す依頼を同じデータベース処理で確定する。送信予定も台帳へ先に保存するoutbox(送信予定表)方式を使い、別の送信担当が未送信行をキューへ届けた後に送信済み時刻を記録する。
状態は「受信」「署名確認済み」「実行待ち」「実行中」「完了」「再試行待ち」「恒久失敗」に分ける。再送を受けたとき、完了済みなら保存済みの結果を返し、実行中なら新しい処理を作らない。失敗時は試行回数、次回時刻、最後のエラー分類を更新する。通信障害だけを再試行し、権限不足や入力不備を無限に繰り返さない。
業務処理側にもイベントIDを渡し、顧客台帳の更新、メッセージ送信、請求処理ごとに一度だけ確定した記録を残す。受信台帳の重複排除だけでは、処理途中の再実行による二重送信を防げないためである。定期点検では、受信済みのまま止まった件数、実行待ちの滞留時間、再試行回数、恒久失敗の未対応件数を確認する。
根拠資料
資料確認日:2026年9月29日。本文中の手順例は編集部の提案で、導入効果の実測値ではありません。

