追記: 2026年6月15日の最新情報
2026年6月15日にOpenAI APIのWebhooks公式ガイドを確認しました。OpenAIのwebhookはStandard Webhooksに沿って配信され、HTTPヘッダーにwebhook-id、webhook-timestamp、webhook-signatureが含まれます。CodexにOpenAI webhook receiverを作らせる場合は、JSONを業務処理へ渡す前に署名検証を置き、signing secretを環境変数やsecret managerで扱う前提をpromptへ入れてください。
OpenAI公式ガイドは、endpointがすぐに2xxを返し、重い処理はbackground workerへ逃がす設計を推奨しています。2xxが返らない、または数秒以内に応答しない場合は最大72時間までexponential backoffで再試行され、重複配送も起こり得ます。実装時はwebhook-idをidempotency keyとして保存し、queue、dead-letter、replay手順までrunbookに含めるのが安全です。確認元はOpenAI API Webhooks guideです。
このテーマをもう少し広げて見るなら、AIエージェントの実行ログ設計: trace・tool call・承認ログを事故調査に使える形で残す と OpenAI Agents SDKを業務アプリに入れる前に: tools・handoffs・guardrails・tracingの設計チェック も合わせて確認してください。Webhook受信後の処理結果、replay、承認ログを事故調査に使える形で残す設計へつなげるため。
3行まとめ
署名。
重複。
非同期。
再送。
Webhookは受けられた時より、重複して遅れて届く時の設計が大事です。
- CodexにWebhook受信を任せる時は、POST endpointを作る前に、signature verification、raw body、event id、重複配送、再送、queue、dead-letterを決めます。
- Webhook payloadは、署名検証が終わるまで信頼できるeventではありません。secretはpromptへ貼らず、環境変数やsecret managerから読みます。
- 受信requestの中で重い業務処理を完了させようとせず、検証、保存、enqueue、ack、非同期処理、replayの流れに分けます。
Webhookは、外部サービスから自分たちのアプリへeventが届く仕組みです。決済完了、Issue作成、メール配信結果、CRM更新、LLM job完了、社内workflow通知。受け取れれば便利ですが、受信処理は意外と壊れやすい領域です。
Codexに「Webhook endpointを作って」と頼むと、route、JSON parse、handler、DB更新まではすぐ作れます。けれど、signature verificationがない、raw bodyが壊れる、同じeventが2回来る、順番が前後する、処理中にtimeoutする、再送で二重反映される、失敗したeventを再実行できない、という問題が残りがちです。
この記事では、CodexにWebhook受信処理を任せる前に決めることを整理します。外向きのAPI連携は公開済みのCodexに外部API連携を任せる前に決めることで扱いました。ここでは、外部から届くeventを受ける側に絞ります。
この記事でわかること
本文。
鍵。
ID。
退避。
Codexへ渡す前に、受信、検証、保存、処理を分けます。
- Webhook受信をCodexへ頼む前に決める7項目
- signature verificationとraw bodyを最初に置く理由
- secretをAIへ貼らずに検証処理を実装する方法
- event idとbusiness keyで二重反映を防ぐ考え方
- queue、dead-letter、replayを使った運用しやすい構成
- 2xxを返すタイミングと、重い処理をrequestから逃がす判断
- Codexへそのまま渡せる依頼packet
- reviewで止めるべき危ない実装
Webhookの仕様はproviderごとに違います。Stripe、GitHub、Slack、Shopify、SendGrid、自社APIでは、署名header、payload、再送条件、timeout、event idの扱いが変わります。この記事は汎用の設計整理であり、実装時には必ず対象providerの公式Docsを確認してください。
前提知識
| 項目 | 内容 | 見方 |
|---|---|---|
| Stripe | 署名。 | |
| GitHub | HMAC。 | |
| Retry | 再送。 | |
| Raw | 未加工。 |
Webhookはproviderごとに署名方法や再送の扱いが変わります。
Webhookは、外部サービスから届くHTTP requestです。外部APIを呼び出す処理とは向きが逆です。自分から取りに行くのではなく、相手が決めたタイミングで届きます。
そのため、受信側は次の前提を持ちます。
- 同じeventが複数回来ることがある
- eventが遅れて届くことがある
- eventの順番が前後することがある
- provider側の再送と、自分たちのreplayが別に存在する
- payloadは署名検証が終わるまで信用しない
- 受信endpointは外部から直接叩かれる入口になる
Webhookは外部から来る命令ではない
Webhookを「外部サービスから来た命令」と扱うと危険です。署名検証後のeventも、ただちに業務状態を変えてよいとは限りません。
たとえば、決済完了eventが来たから即座に権限付与するのか、注文DBの状態と照合するのか、既に処理済みなら無視するのか。ここはプロダクトの業務ルールです。Codexに任せる前に、人間が決めます。
raw bodyを壊さない
StripeのWebhook署名検証では、受信したraw bodyが必要です。GitHubのWebhook署名検証でも、payload bodyとsecretからHMACを計算し、headerと比較します。
frameworkのmiddlewareが先にJSON parseしたり、bodyを文字列化し直したりすると、署名検証が失敗することがあります。Codexへは「署名検証前にbodyを加工しない」と明記します。
受信前に決める7項目
| 項目 | 内容 | 見方 |
|---|---|---|
| Provider | 送信元。 | |
| Route | 入口。 | |
| Secret | 署名。 | |
| Event | 種類。 | |
| Store | 保存。 | |
| Queue | 処理。 | |
| Replay | 再送。 |
Webhookは小さなPOST endpointではなく、event処理の入口です。
Webhook routeを作る前に、7項目を決めます。
| 項目 | 決めること | 曖昧なまま起きること |
|---|---|---|
| provider | どのサービスから受けるか | 署名方式が混ざる |
| route | endpoint URLとmethod | 1つのrouteに責務が集まる |
| secret | 検証用secretの置き場所 | promptやログへ露出する |
| event type | 処理するeventの種類 | 不要なeventまで処理する |
| store | event idとpayloadの保存 | 重複や失敗を追えない |
| queue | 非同期処理の入口 | request中に重い処理をする |
| replay | 再送・再実行の手順 | 失敗時に手作業で壊す |
Codexへ渡す依頼でこれらが空欄だと、実装中に都合のよい決め打ちが入ります。Webhookは届くかどうかだけでなく、届いた後に安全に処理できるかが本題です。
providerごとにrouteを分ける
StripeとGitHubを同じWebhook routeで受ける必要はありません。signature header、event schema、retry、secretが違うため、providerごとにrouteを分けたほうがreviewしやすくなります。
event typeを絞る
受け取るevent typeは必要なものだけにします。決済なら checkout.session.completed、GitHubなら issues や pull_request のように、処理対象を絞ります。不要なeventは保存だけするか、拒否するか、明示的に無視します。
reviewで見ること
差分reviewでは、指定していないevent typeを処理していないか、署名検証より前にpayloadを信用していないか、secret名がハードコードされていないかを見ます。
signature verificationを最初に置く
- 1Raw
保持。
- 2Header
取得。
- 3HMAC
計算。
- 4Compare
比較。
- 5Reject
拒否。
署名検証前のpayloadは、まだ信頼できるeventではありません。
Webhook endpointでは、signature verificationを最初に置きます。JSON parse、DB更新、queue投入、ログ出力より先です。
GitHub Docsでは、Webhook secretを設定した場合に X-Hub-Signature-256 headerを使い、HMAC-SHA256で検証することが説明されています。Stripe Docsでも、payload、Stripe-Signature header、endpoint secretを使った署名検証が説明されています。
Codexへは、次のように指示します。
署名検証前にpayloadを業務処理へ渡さない。
raw bodyを保持したままsignature verificationを行う。
secret値はpromptへ貼らず、WEBHOOK_SECRET環境変数から読む。
検証失敗時はpayload内容を詳細にlogしない。
署名検証を通過したeventだけ保存とqueue投入へ進める。
secretはpromptへ貼らない
Webhook secretは、AIへ貼る必要がありません。必要なのは、環境変数名、provider、header名、検証方式、失敗時の扱いです。
secretをpromptへ貼ると、会話、ログ、artifact、PR commentに残る可能性があります。Codexにはダミー値と環境変数名だけ渡します。
timing safe comparisonを使う
署名比較では、単純な文字列比較ではなく、実行時間差を利用されにくい比較方法を使います。多くの公式SDKや標準libraryには安全な比較関数があります。Codexへは、公式SDKがある場合はそれを優先し、手書きする場合はtiming safe comparisonを使うよう指示します。
注意点
署名検証の失敗理由を外部に細かく返しすぎないでください。responseは短く、内部logはsecretやpayloadをmaskします。
idempotencyと重複配送を前提にする
| 項目 | 内容 | 見方 |
|---|---|---|
| Event ID | 受信。 | |
| Status | 状態。 | |
| Business key | 反映。 | |
| Lock | 競合。 |
同じeventが複数回来ても、業務状態は一度だけ進むようにします。
Webhookは、同じeventが複数回来ることがあります。providerの再送、手動redelivery、自分たちのreplay、network timeoutなどが理由です。
そのため、Webhook処理はidempotentにします。同じeventをもう一度受けても、業務状態が二重に進まないことが重要です。
event idで処理済みを記録する
まず、providerが渡すevent idやdelivery idを保存します。受信時刻、provider、event type、signature verification結果、processing status、attempt、error reasonを残します。
同じevent idが来たら、既に処理済みか、処理中か、失敗済みかを見ます。処理済みなら二重処理せず、必要に応じて2xxを返します。
business keyでも二重反映を防ぐ
event idだけでは足りない場合があります。たとえば、同じ注文IDに対して別eventが複数届く、似た意味のeventが順不同で届く、provider側で別IDとして再生成される場合です。
その場合は、注文ID、invoice ID、user ID、subscription IDのようなbusiness keyでも二重反映を防ぎます。
評価基準
同じeventを2回、同時に、順番を変えて処理しても、業務状態が壊れないなら良い設計です。Codexへは、このテストをfixtureで作らせます。
queueへ渡してすぐ返す
- 1Verify
検証。
- 2Persist
保存。
- 3Enqueue
投入。
- 4Ack
応答。
- 5Work
処理。
重い処理を受信requestの中に詰め込まない構成にします。
Webhook endpointの中で重い処理をすべて終わらせようとすると、timeoutや再送が増えます。受信requestでは、署名検証、event保存、queue投入までにして、重い処理はworkerへ渡します。
基本の流れはこうです。
1. raw bodyを受ける
2. signature verificationを行う
3. event idとpayloadを保存する
4. queueへjobを入れる
5. providerへ2xxを返す
6. workerで業務処理する
7. 成功、失敗、retry、DLQを記録する
2xxを返すタイミング
2xxを返すのは、eventを安全に保存し、後続処理へ渡せる状態になった時です。業務処理が完了してから2xxにすると、処理が遅い時にprovider側が失敗と見なし、再送が増える可能性があります。
ただし、保存にもqueue投入にも失敗した場合に2xxを返すと、eventを失います。ここは明確に分けます。
dead-letterを残す
workerで処理に失敗したeventは、一定回数のretry後にdead-letterへ送ります。dead-letterには、event id、provider、event type、失敗理由、attempt、最後のerror、次の対応を残します。
実装で見ること
Webhook handler内で外部API呼び出し、メール送信、重いDB更新、長いtransactionをしていないかを見ます。受信と業務処理は分けます。
replayと運用ログを設計する
- Receive
受信。
- Fail
失敗。
- DLQ
退避。
- Inspect
確認。
- Replay
再実行。
replayは新規実行ではなく、同じeventを安全にやり直す操作です。
Webhookは失敗します。失敗した時に、再送やreplayができる設計にします。
providerによっては、管理画面やAPIからredeliveryできます。一方で、自分たちのDBに保存したeventをworkerへ再投入するreplayもあります。この2つを混同しないようにします。
再送は新規処理ではない
redeliveryやreplayは、新規eventとして扱わないほうが安全です。同じevent idとして扱い、processing statusを見て、再実行してよい状態かを確認します。
runbookへ残す
replayは運用操作です。誰が、どのeventを、なぜ、いつ再実行したかを残します。大量replayする場合は、rate limit、外部APIへの負荷、ユーザーへの影響も見ます。
Codexへ渡す依頼packet
| 項目 | 内容 | 見方 |
|---|---|---|
| Allowed | 許可。 | |
| Forbidden | 禁止。 | |
| Verify | 署名。 | |
| Tests | 確認。 | |
| Runbook | 運用。 |
packet化すると、署名検証や重複処理を実装漏れにしにくくなります。
Codexへは、次のようなpacketで渡します。
目的:
payment providerから決済完了Webhookを受け、注文をpaidへ進める。
provider:
Stripe。署名検証は公式SDKを優先する。
route:
POST /api/webhooks/stripe
secret:
STRIPE_WEBHOOK_SECRET を環境変数から読む。値はpromptへ貼らない。
受けるevent:
checkout.session.completed のみ処理する。
その他eventは保存してignoredにする。
検証:
raw bodyを署名検証前にparseしない。
検証失敗時は400を返し、payload本文はlogしない。
idempotency:
event.idを保存し、処理済みeventは二重処理しない。
order_idでもpaid反映を一度だけにする。
queue:
署名検証、保存、enqueue後に2xxを返す。
業務処理はworkerで行う。
test:
valid signature、invalid signature、duplicate event、unknown event、worker failure、replayをfixtureで確認する。
review:
secret露出、署名検証前のparse、同期重処理、重複処理漏れ、DLQなしを重点的に見る。
このpacketがあると、Codexは実装すべき境界を理解しやすくなります。Webhookは小さなrouteに見えますが、実際にはevent ingestionの入口です。
よくある失敗
未検証。
加工済み。
重複。
重い処理。
Webhookの失敗は、テスト時に届いた1回のeventだけでは見えにくいです。
signature verificationを後回しにする
「まず動くようにしてから署名検証を足す」は危険です。署名検証は後付けしにくく、middleware構成やraw bodyの扱いに影響します。
JSON parse済みbodyで検証する
frameworkが先にbodyをparseすると、providerが署名したraw bodyと違う形になることがあります。Webhook routeだけbody parserを分ける必要があります。
event idを保存しない
event idを保存しないと、重複配送やreplayを安全に扱えません。処理済みかどうかを判断できないため、二重反映が起きます。
受信request内で重い処理をする
Webhook request中に外部API呼び出しや重いDB処理を詰め込むと、timeoutや再送の原因になります。受信と処理を分けます。
2xxを早く返しすぎる
保存もqueue投入もできていないのに2xxを返すと、providerは成功と見なし、eventが消えます。2xxは安全に後続処理へ渡せる状態になってから返します。
FAQ
| 項目 | 内容 | 見方 |
|---|---|---|
| 200? | 保存後。 | |
| Retry? | 前提。 | |
| Secret? | 環境。 | |
| Replay? | 同一。 |
迷ったら、同じeventがもう一度来ても安全かを確認します。
署名検証は必須ですか
外部から直接届くWebhookでは、基本的に必須と考えます。providerが提供する署名検証やsecret設定を使い、署名検証前のpayloadを信用しない構成にします。
すぐ2xxを返すべきですか
署名検証、event保存、queue投入が終わったら2xxを返す構成が扱いやすいです。保存できていない、またはqueueへ渡せない状態で2xxを返すのは避けます。
同じeventが2回来たらどうしますか
event idで処理済みを見て、二重処理を避けます。業務状態の反映はbusiness keyでも守ります。同じeventを何度受けても壊れないようにします。
Codexにsecretを渡さずに実装できますか
できます。環境変数名、header名、検証方式、fixture、期待する失敗処理を渡せば、secret値なしで実装できます。
replayは誰が実行しますか
本番では運用者の明示操作にします。自動retryと手動replayを分け、操作ログ、対象event、理由、結果を残します。
次に読むなら
参照した主な情報源
- Stripe Docs: Receive Stripe events in your webhook endpoint
- Stripe Docs: Resolve webhook signature verification errors
- GitHub Docs: Validating webhook deliveries
- GitHub Docs: Handling webhook deliveries
- OpenAI Developers: Codex Security
更新履歴
- 2026.06.01
初版。
providerのWebhook仕様は変わるため、導入時に公式Docsを確認します。
- 2026.06.01: 初版公開。CodexへWebhook受信を任せる前のsignature verification、idempotency、queue、replayを整理しました。
