仕組み
- Webhook 相関のため、タスク呼び出しごとに一意のオペレーション ID が生成される
- あなたのレシーバー向けに Webhook URL が構築される。URL は自身のルーティングトークンを含んでよいが、セラーにとっては不透明である
push_notification_configが URL と明示的なoperation_idを伴ってタスクリクエストボディに注入される — 共有シークレットは不要- タスクステータスが変わるとセラーがあなたの URL に Webhook 通知を POST する。各 POST は、自身の brand.json の
agents[]エントリで公開したadcp_use: "request-signing"鍵で署名される。非推奨のwebhook-signing鍵は互換期間中も受け入れられる - あなたはセラーが公開する JWKS に対して署名を検証し、
idempotency_keyで重複排除する - 各通知はペイロードで明示的な
operation_idをエコーバックするため、URL を解析せずに相関できる
@adcp/sdk ライブラリを使用している場合、このフロー全体が自動的に処理されます。バイヤーとしては、クライアントに webhookUrlTemplate と自身のエージェント URL を設定します。push_notification_config がすべての送信タスク呼び出しに注入され、受信 Webhook はセラーの JWKS に対して自動的に検証されます。Webhook を発行するセラーとしては、brand.json の agents[] エントリに署名 JWK を公開します。新しい署名者は adcp_use: "request-signing" を使用します。Webhook 専用の鍵素材が欲しい場合は、別個の kid を持つ 2 つ目の request-signing JWK を公開します。
:::warning レガシー HMAC フォールバック(非推奨)
RFC 9421 Webhook プロファイルをまだ採用していないレシーバーと統合するバイヤーは、push_notification_config.authentication.credentials を設定することでレガシー HMAC-SHA256 スキームにオプトインしてもよい(MAY)。そのパスは非推奨であり AdCP 4.0 で削除されます — 下記の レガシー HMAC-SHA256 フォールバック を参照してください。Webhook を登録するインバウンドリクエストは 3.0 では通常 9421 署名されないため、authentication ブロックは経路上の除去/注入を受けやすい — 運用上の緩和策は ダウングレードと注入への耐性を参照してください。
:::
命名: snake_case vs camelCase
これは人を混乱させます。2 つの命名規則が関係しています:
AdCP のフィールド名は常に
push_notification_config(snake_case)です。他のタスクパラメーターと並んでタスクリクエストボディに入ります。
A2A では、A2A プロトコルが camelCase を使う configuration エンベロープでこれをラップします — が、オブジェクトの中身は同一です。
リクエストへの push_notification_config の追加
MCP
push_notification_config をタスク引数として、他のタスクパラメーターとマージして含めます:
authentication はデフォルトケースでは省略されます — セラーは自身の adcp_use: "request-signing" 鍵で署名します。非推奨の webhook-signing 鍵は互換期間中も受け入れられます。レガシー HMAC-SHA256 フォールバックが必要な場合のみ authentication.credentials を含めます。
A2A
A2A では、スキルパラメーターはmessage.parts[].data.parameters に残ります。プッシュ通知設定はトップレベルの configuration オブジェクトに入ります:
オペレーション ID と URL テンプレート
オペレーション ID により、受信 Webhook を正しいタスク呼び出しに相関できます。パターン:- バイヤーがタスク呼び出しごとに一意のオペレーション ID を生成する
- バイヤーがそれを
push_notification_config.operation_idとしてセラーに引き渡す。Webhook URL の構造はバイヤーの選択であり、セラーにとって不透明である - セラーがすべての Webhook ペイロードで
operation_idをそのままエコーする — URL 解析は不要
- バイヤーは(SHOULD) すべての Webhook 登録についてセラーに
operation_idを供給し、タスク呼び出しごとに一意の値を生成すべきです(UUID 推奨)。セラーはoperation_idを省略した Webhook 登録をINVALID_REQUESTで拒否してもよい(MAY)。 - セラーは(MUST) すべての Webhook ペイロードで、バイヤーが供給した
operation_id値を受け取ったとおり正確にエコーしなければなりません。ペイロードフィールドが相関の唯一の真実の源です。 - セラーは(MUST NOT)
push_notification_config.urlを解析してoperation_idを導出してはなりません — URL 構造(パステンプレート、クエリパラメーター、不透明トークンなど)はセラーの視点からは実装依存であり、実装をまたいで確実に逆算できません。バイヤーの URL 規約はプロトコルの一部ではありません。 - レシーバーは(MAY) URL パスまたはクエリ文字列で HTTP エンドポイントをディスパッチしてもよいが、URL 由来の値をオペレーション相関キーとして使用してはなりません(MUST NOT)。ワイヤーレベルの相関識別子はペイロードフィールドです。
nurl/burl、VAST トラッキングピクセル、A2A の PushNotificationConfig)が示す先例と一致します: HTTP 呼び出しを発火するエンティティは、相関データのためにレシーバーの URL を決して解析しません。
URL テンプレートパターン(バイヤー側の規約のみ):
?route=…、フラットなパス、または完全に不透明なトークンを好むバイヤーも、セラー側の operation_id が SDK の送信側 API を通じて供給される限り、完全にコンフォーマントです。
例(クライアントライブラリが自動処理):
"operation_id": "cd51e063-2b79-4a6d-afac-ed7789c3a443" が含まれるため、ハンドラーはペイロードフィールドを直接読むことで正しい保留中のオペレーションに相関できます。URL パスは HTTP ハンドラーを選択できますが、プロトコルのオペレーション ID は選択できません。
セラー SDK 実装は、送信側 Webhook API の明示的なパラメーターとして operation_id を公開します(例: Python WebhookSender.send_mcp(url=…, operation_id=…))。セラーのアプリケーションコードが元のタスクリクエストから Webhook 発火へ値を引き通します。SDK は URL からそれを回復しようとは決してしません。
呼び出し元の context オブジェクトのエコー
発信元のリクエストがトップレベルの context オブジェクトを運んだ場合、セラーは同じオペレーションのすべての Webhook ペイロードで、operation_id と並んでその同じオブジェクトをそのままエコーしなければなりません(MUST)。これは同期および非同期ステータスレスポンスに適用されるのと同じ契約です — コンテキストとセッション — 規範的なエコー契約を参照してください。エコーは working、input-required、completed、failed、canceled の配信を通じて引き継がれなければなりません(MUST)。初回レスポンスと後の Webhook の間で context を落とすと、まさに最も必要とされる箇所でバイヤー側の相関が壊れます。context.trace_id または context.internal_campaign_id でルーティングするバイヤーは、すべての配信でのそのままのエコーに依存します。
Webhook が発火するとき
Webhook は、push_notification_config がリクエストにある限り、初回レスポンス後の各ステータス変更ごとに送信されます。
タスクが同期的に完了する場合(初回レスポンスがすでに completed、failed、rejected などの終端状態)、Webhook は送信されません — すでに結果を持っているからです。
初回レスポンスが非終端(working または submitted)であるオペレーションのみが、後で AdCP タスク Webhook を発行できます。インラインの終端レスポンスに対して、セラーは task_id を捏造したりインライン結果を push_notification_config.url にリプレイしたりしてタスク Webhook を合成してはなりません(MUST NOT)。同期専用オペレーション、および push_notification_config が意味を持たない同期専用オペレーションモードは、代わりにそのフィールドを整形式のランタイムエラーとして拒否してもよい(MAY)。これはセラー対バイヤーのワイヤールールです: バイヤー SDK は同期レスポンスをローカルのコールバック、Promise、ハンドラー呼び出しに正規化してもよい(MAY)が、それらのローカル SDK の便宜は AdCP Webhook ではありません。将来の AdCP バージョンがバイヤー通知の同期完了通知モードを追加する場合、それは明示的でケイパビリティ宣伝されます。3.x のタスク Webhook 契約はそれを定義しません。
Webhook をトリガーするステータス変更:
Webhook ペイロード形式
MCP
idempotency_key を運びます — 同じイベントのリトライをまたいで安定する、送信者生成の鍵です。これは正準の重複排除フィールドです。下記の 信頼性 を参照してください。
ペイロードの構造: エンベロープ vs. result
Webhook レシーバーは、ワイヤーエンベロープとタスク固有の result を区別しなければなりません。完全な MCP Webhook エンベロープは、HTTP POST ボディとして送信される JSON オブジェクトです。デリバリーレポートの内容はresult の下に存在します。それ自体はトップレベルの POST ボディとしては有効ではありません。
デリバリーレポートの発火では、完全なワイヤーペイロードは次のようになります:
status は非同期 Webhook ステータス(completed、failed、working など)です。ネストされた media_buy_deliveries[].status はメディアバイのライフサイクルまたはレポート状態(active、paused、reporting_delayed など)です。2 つのフィールドを混同しないでください。
scheduled、final、delayed、adjusted などのデリバリーレポートデータイベントでは、notification_id は設計上不在です。トランスポートイベントは idempotency_key で重複排除してください。aggregated_totals フィールドは get_media_buy_delivery レスポンス専用の API 専用であり、レポート Webhook の result ペイロードで発行してはなりません。
署名とコンテンツダイジェストは、この完全なエンベロープとして送信される正確な生の JSON バイト上で計算されます。検証前に result オブジェクトだけを再シリアライズしたり、空白を追加したり、フィールド順を変えたりすると、バイトが変わり署名検証が壊れます。
A2A
A2A はTask オブジェクト(最終状態向け)または TaskStatusUpdateEvent(進捗向け)を送信します。最終状態(completed、failed)では、AdCP result データは .artifacts[0].parts[] にあります。中間状態(working、input-required)では、データは status.message.parts[] にあります。
プロトコル比較
登録チャネルがエンベロープ形状を決定する
Webhook エンベロープの形状は、同期リクエストがどのトランスポートで送られたかではなく、バイヤーがどの登録メカニズムを使ったかで決まります:
2 つのチャネルは独立しています。バイヤーは同じタスクについて両方を登録し、ステータス変更ごとに両方の Webhook を受け取ってもよい(MAY)。
なぜこれがモデルであり「インバウンドトランスポートに合わせる」ではないのか。 各チャネルはそのエンベロープ専用に作られています: AdCP
push_notification_config は AdCP mcp-webhook-payload 形状のための AdCP 層の登録、A2A TaskPushNotificationConfig は A2A 自身の StreamResponse ラップ配信のための A2A 層の登録です。バイヤーはレシーバーに合うチャネルを選びます — 判別子フィールドは不要で、オーバーライドすべき曖昧さもありません。
典型例: A2A で同期、AdCP 形状の Webhook。 MCP ネイティブのランタイムからオーケストレーションし、1 つの特定の高スループット同期オペレーションに A2A を使うバイヤーは、その SendMessage ボディ内の AdCP タスク引数に push_notification_config を入れます。セラーは、同期トランスポートが A2A であることに関わらず、それを AdCP 形状の登録として尊重します。バイヤーのレシーバーは、パイプライン内の他のすべての AdCP Webhook と同じ mcp-webhook-payload 形状を受け取ります。
A2A 形状の Webhook を望む A2A バイヤーは、A2A のネイティブプッシュ通知メカニズムを通じて登録します。AdCP はそのケースのために何も追加する必要はありません。
ステータス別の result データ
署名検証
すべての AdCP 3.0 Webhook は RFC 9421 Webhook プロファイルの下で署名されます。セラーは、自身の brand.json のagents[] エントリで公開した adcp_use: "request-signing" 鍵で署名します。非推奨の webhook-signing 鍵は互換期間中も受け入れられます。あなたはセラーが公開する JWKS に対して検証します。共有シークレットはワイヤーを渡りません。
パブリッシャーは 3 つのヘッダーを送ります(Content-Type に加えて):
@method、@target-uri、@authority、content-type、content-digest。content-digest は REQUIRED です — ボディがイベントそのものであり、それをカバーしない署名は重要な攻撃面を保護していません。
検証は、14 ステップのリクエスト検証器チェックリストに、3 つの Webhook 置換を加えたものに従います:
- エラーコードは
webhook_signature_*プレフィックスを使う(Webhook エラータクソノミーを参照)。 tagはadcp/webhook-signing/v1でなければならない(MUST)。keyidはセラーオペレーターのbrand.jsonのagents[].jwks_uriを介して解決し、存在する場合はパブリッシャーのadagents.jsonのsigning_keys[]ピンを適用する(統合からセラーのエージェント URL はすでに持っている)。
Content-Digest の検証(チェックリストのステップ 11)には、生の HTTP ボディバイトが必要です。JSON パースの前にそれらをキャプチャしてください — いかなる再シリアライズもダイジェストの一致を壊します。
Express では:
created/expires/nonce の sig-params は、5 分の最大有効ウィンドウと (keyid, nonce) のリプレイ重複排除を強制します。keyid ごとの上限とメモリ制限ルールは トランスポートリプレイ重複排除を参照してください。
:::
レガシー HMAC-SHA256 フォールバック(非推奨)
:::warning 非推奨 — AdCP 4.0 で削除 下記の HMAC-SHA256 スキームは 3.x のみの互換性のための便宜です。新しい統合はpush_notification_config.authentication を省略し、上記の 9421 Webhook プロファイルを使用すべきです(SHOULD)。セラーはレガシースキームのサポートを断ってもよい(MAY)。
:::
バイヤーは push_notification_config.authentication.credentials を設定することで HMAC-SHA256 にオプトインできます。設定されている場合、セラーは共有シークレットを使って HMAC-SHA256 で署名し、リプレイ保護のためにタイムスタンプを含めます。
設定(レガシー):
{unix_timestamp}.{raw_json_body} — Unix タイムスタンプ(秒)、ドット、次に HTTP ボディで送信される正確な JSON バイトです。
rawBody はワイヤー上で送信される正確なバイトでなければなりません。JSON ペイロードをシリアライズしてボディを生成する際は、コンパクトなセパレーター("," と ":"、周囲の空白なし)を使用してください — これは JavaScript の JSON.stringify とほとんどの HTTP クライアントのデフォルトに一致し、レシーバーが raw_body として見るものです。ここでのよくあるクロス SDK の失敗は、署名者が空白を挿入する言語デフォルト(例: Python json.dumps(payload))を呼ぶ一方、HTTP クライアントがコンパクトなバイトをワイヤーに書き込むケースです — 署名者はレシーバーが決して見ないバイト上で署名します。バイト等価性のために json.dumps(payload, separators=(",", ":"))(または同等物)を使用してください。正準のワイヤー形式と検証器入力の扱いに関する完全なルールは Webhook セキュリティ — レガシー規範ルールを参照してください。
パブリッシャー実装(レガシー):
レガシー Bearer トークン(非推奨)
A2A のauthentication.schemes: ["Bearer"] スキームも互換性のためにサポートされ、AdCP 4.0 で削除されます。Bearer はボディに対する改ざん保護を提供しません。9421 プロファイルは署名者アイデンティティ(JWKS アンカー、ローテーション可能、失効可能)と鍵管理(ワイヤー上に共有シークレットなし)でより強力です。ボディ完全性の保護は、両者ともボディバイトをカバーするためレガシー HMAC スキームと同等です。セラーは変更系コールバックについて Bearer を拒否すべきです(SHOULD)。
信頼性
Webhook は少なくとも 1 回の配信を使用します — 同じイベントを複数回受け取ることがあり、イベントは順不同で到着することがあります。idempotency_key による重複排除
すべての Webhook ペイロード — MCP タスクエンベロープ、ガバナンスのリスト変更 Webhook(collection_list_changed、property_list_changed)、アーティファクトプッシュ Webhook、権利の revocation-notification — は必須の idempotency_key を運びます。パブリッシャーはこの鍵を個別のイベントごとに 1 回生成し、すべてのリトライで再利用します。レシーバーはそれで重複排除しなければなりません(MUST)。
送信者の要件:
- 鍵は暗号学的にランダムでなければなりません(MUST、UUID v4 推奨)。連番、タイムスタンプのみ、その他の予測可能な値は非コンフォーマントです: レシーバーは生の値で重複排除するため、予測可能な鍵は攻撃者がレシーバーのキャッシュに事前投入して後の正当なイベントを抑制することを可能にします。
- 鍵は同じイベントのリトライをまたいで安定でなければならず(MUST)、個別のイベントに再利用してはなりません(MUST NOT)。
- 重複排除のスコープは
(認証された送信者アイデンティティ, idempotency_key)です。「認証された送信者アイデンティティ」とは、署名検証によって確立された送信者の暗号学的アイデンティティを意味します — 9421 デフォルトでは、解決されたkeyid→ 署名者のagents[]エントリ URL、レガシーフォールバックでは、検証された HMAC シークレットまたは Bearer トークンからのクレデンシャルバインディング。アイデンティティをペイロードフィールドから導出してはなりません。異なる送信者からの鍵は独立したキースペースに保たなければならず(MUST)、複数のセラーと統合するレシーバーはそれらを統合してはなりません(MUST NOT)。HMAC→9421 移行中は、レシーバーは同じ論理セラーの両方の送信者アイデンティティ形式を 1 つのキースペースにマップし、スキームをまたぐ重複も重複排除されるようにすべきです(SHOULD)。 - クロスエンドポイント重複排除(MUST)。 複数の Webhook エンドポイント(統合ごと、環境ごと、テナントごと、または水平スケールされたフリートのポッドごと)を公開するレシーバーは、ある送信者が到達できるすべてのエンドポイントにわたって
(送信者アイデンティティ, idempotency_key)キースペースを共有しなければなりません(MUST) — ポッドごとのインメモリキャッシュは非コンフォーマントです。共有ティアがなければ、同じ署名済みイベントが兄弟エンドポイントにリプレイされると 2 回実行されます。(keyid, nonce)スコープに関するトランスポート層の対応ルールは Webhook リプレイ重複排除のサイジングを参照してください。 - 重複排除の状態は、プロセス再起動、ポッド置換、リージョンフェイルオーバーを生き延びる耐久ストレージに少なくとも 24 時間永続化しなければなりません(MUST)。パブリッシャーはそのウィンドウを超えてリトライすべきではありません(SHOULD NOT)。レシーバーの TTL 後に到着するリトライは新しいイベントとして再処理されます。インメモリのみのキャッシュ(バッキングティアなしのポッドごとの
Mapまたは LRU)は非コンフォーマントです — 約 360 秒の署名ノンスウィンドウと 24 時間の冪等性ウィンドウの非対称性が、変位リプレイウィンドウを作ります。そこでは、正当な署名済みリトライ(新しいノンス、同じidempotency_key)が署名検証を通過し、レシーバーがインメモリ状態を落としたためキャッシュエントリを見つけられません。副作用が 2 回実行されます。キャッシュティアが 24 時間を耐久的に守れないレシーバーは、統合するすべての送信者に、より短い実効ウィンドウを文書化しなければなりません(MUST) — 黙って短縮するのが危険なモードです。 - レシーバーは送信者ごとに重複排除キャッシュサイズを制限し、無制限に成長させるのではなく
429 Too Many Requestsを返す(または接続を切る)べきです(SHOULD) — 高ボリュームの新しい鍵を発行する誤動作または敵対的なセラーは、さもなくばストレージ増幅のベクトルになります。 - 重複は
2xx(通常200 OK)で応答しなければならず(MUST)、409 Conflictではありません。少なくとも 1 回の送信者は、2xx 以外のレスポンスを「配信失敗」と解釈し、指数バックオフでリトライします。正常に重複排除されたイベントに4xxを返すと、正しいレシーバーの挙動がリトライ嵐に変わります。重複はエラーではなく no-op です。 - Webhook レシーバーは、鍵再利用をまたいだペイロード等価性を検証しません。送信者が変更されたペイロードで鍵を再利用した場合(送信者のバグ)、レシーバーのキャッシュされた最初のコピーが勝ち、2 つ目は黙って重複排除されます。これはリクエスト側の
IDEMPOTENCY_CONFLICTの挙動とは異なります — 送信者は個別のイベントごとに新しい鍵を生成することについて単独で責任を負います。
発火漏れの診断
バイヤーが Webhook がエンドポイントに届いていないと疑う場合 — ゲートウェイの 5xx、古いシーケンスの重複排除、ドリフトした Webhook URL、作動したサーキットブレーカー下での発火抑制 —get_media_buys を include_webhook_activity: true で呼び出します。返される各メディアバイは、呼び出し元プリンシパルの最近の発火の webhook_activity 配列を運びます。これには idempotency_key(ペイロードの重複排除キーと一致 — 自身のエンドポイントログと照合)、status(success / failed / timeout / connection_error / pending)、http_status_code、attempt、error_message が含まれます。スコープは呼び出し元プリンシパル自身の発火です。オペレーターチケットは不要です。
ベストプラクティス
- 必ずバックアップとしてポーリングを実装する — Webhook は失敗し得る。Webhook が設定されている場合は間隔を減らして(例: 2 分ごと)ポーリングし、終端ステータスを受け取ったら停止する
idempotency_keyで重複排除する — すべてのペイロードはリトライをまたいで安定する必須の鍵を運ぶ。処理済みの鍵を少なくとも 24 時間追跡する- 重複には 2xx を返す — 正常に重複排除されたイベントはエラーではなく no-op。2xx 以外を返すと送信者のリトライバックオフをトリガーしリトライ嵐を作る
- 処理前に署名を検証する — いかなる副作用の前にも 9421 Webhook 検証器チェックリスト(またはオプトインした場合はレガシー HMAC チェック)を実行する
- 即座に確認応答する — セラーのタイムアウトと不要なリトライを避けるため、重い処理の前に
200を返す - URL 構造に依存しない — ビジネス相関にはペイロードの
operation_idを使う。URL パスはエンドポイントの多重分離のみに使ってよい - 4.0 での HMAC 削除に備える — 現在レガシー HMAC フォールバックを使っている場合、3.x の間に 9421 Webhook プロファイルへ移行する
ペイロード抽出
Webhook レシーバーは形式を検出し AdCP データを抽出する必要があります。バイヤーはトランスポートを設定したため通常は形式を知っていますが、防御的な検出はマルチフォーマットレシーバーに有用です。形式検出
抽出
MCP Webhook:result フィールドから直接データを抽出します。
A2A Webhook: A2A レスポンス抽出アルゴリズムを使用します — 最終状態は .artifacts[0].parts[](最後の DataPart)から、中間状態は status.message.parts[](最初の DataPart)から抽出します。
セキュリティ要件
- Content-Type 検証: 送信者は
application/jsonを送らなければなりません(MUST)。レシーバーは署名検証の前に他のタイプを拒否しなければなりません(MUST)。 - ペイロードサイズ制限: レシーバーは 1MB 制限を強制すべきです(SHOULD)。署名検証の前に拒否します — 大きなペイロード上でダイジェストや HMAC を計算するのは DoS ベクトルです。
413 Payload Too Largeを返します。 - 重複排除:
idempotency_keyが正準の重複排除フィールドです。署名検証(9421 またはレガシー HMAC)とリプレイ重複排除がトランスポートを保護し、idempotency_keyがアプリケーション層で重複する副作用から保護します。 - 形式検出: 自動検出は防御的なフォールバックです。レシーバーはペイロード検査のみに頼るのではなく、トランスポート設定からの既知の形式(
knownFormatパラメーター)を使用すべきです(SHOULD)。侵害された中間者が、抽出を誤ったパスにルーティングする曖昧なペイロードを作る可能性があります。
テストベクター
機械可読のテストベクターは/static/test-vectors/webhook-payload-extraction.json で利用できます。クライアントライブラリは、形式検出と抽出のロジックをこれらのベクターに対して検証すべきです(SHOULD)。
レポート Webhook
レポート Webhook はタスクステータス Webhook とは別です。アクティブなメディアバイの定期的なパフォーマンスデータを配信し、push_notification_config ではなく create_media_buy の reporting_webhook を通じて設定されます。
reporting_webhook の詳細は Task Reference を参照してください。
永続チャネル契約
タスク Webhook は論理タスクごとに 1 回発火し、タスクが確定すると停止します。永続 Webhook — メディアバイ上のreporting_webhook と push_notification_config — は単一のオペレーションより長く続き、リソースの寿命の間繰り返し発火します。以下の契約は永続チャネルに適用されます。
このセクションは スナップショットとログ契約のトランスポート側の半分です。読み取り側のルール(スナップショットが権威、リプレイ = 再読み取り)は、そのページを参照してください。
配信セマンティクス
- 少なくとも 1 回の配信。 セラーはリトライ下で同じ論理イベントを再発火してもよい(MAY)。レシーバーは
idempotency_keyでトランスポートのリトライを重複排除しなければなりません(MUST)。型付きnotification_idも運ぶ状態形状イベント(mcp-webhook-payload.jsonと snapshot-and-log Rule 1 を参照)については、レシーバーは発火を現在のスナップショット状態に相関させるためnotification_idも追跡しなければなりません(MUST) — 同じnotification_idを 2 つの異なるidempotency_key値の下で見ることは、トランスポートのリトライではなく再発行のシグナルです。 - 順序保証なし。 同じリソース上の 2 つのイベントが数秒以内に順不同で到着してもよい(MAY)。レシーバーは Webhook の順序を正準として扱うのではなく、リソーススナップショットを通じてリコンサイルしなければなりません(MUST)。
- 冪等な適用。 同じペイロードを 2 回適用しても、結果のレシーバー状態は同一でなければなりません(MUST)。
合体(Coalescence)
状態形状のイベントタイプについて、セラーは同じリソース上の複数のほぼ同時の変更を単一のプッシュに合体させるべきです(SHOULD)。合体ウィンドウはイベントタイプごとであり、一律の上限ではありません — レイテンシーに敏感なイベント(不正、ブランドセーフティ)は、アドバイザリと同じウィンドウを待てません。
セラーは、デフォルト未満のレイテンシーを必要とするレシーバー向けに、
get_agent_capabilities を通じてより短い合体ウィンドウを宣言してもよい(MAY)。セラーは、レシーバー側で宣言された明示的なバイヤーのオプトインなしに、タイプごとのデフォルトを超えてはなりません(MUST NOT)。デリバリーレポートの発火(scheduled、final)は独自のケイデンスに従い、この合体ルールの対象外です。
リプレイと回復
バイヤーのレシーバーがオフラインで発火を逃した場合、回復はスナップショットを読むことです。すべての永続チャネルに 2 つのパスが存在し、内容は同等です:impairmentイベントを逃した →get_media_buysを呼んでimpairments[]を読む(完全な状態回復)。- デリバリーレポートの発火を逃した → 該当ウィンドウについて、セラーが
reporting_capabilities.windowed_pull_granularities(#4590)で宣言した粒度にtime_granularityを設定してget_media_buy_deliveryを呼ぶ。プルは Webhook が配信したのと同じウィンドウごとのスライスを返す。ウィンドウ粒度をまだ宣言していないセラーは、日付範囲の集計と日次内訳のみを返し、サブ日次の発火を再構築できない。 - その他の状態形状イベントを逃した → 対応する
get_*タスクを呼ぶ。
get_media_buys 上の webhook_activity[]、#4278 で提案)は、デバッグのために保持ウィンドウ内の最近の発火を公開します — バイヤーはこれを使って、セラーが発火したこととレシーバーが返した HTTP ステータスを検証します。それはデータ回復チャネルではありません。それはスナップショットのウィンドウごとのプル(#4590)の役割です。
可変性とローテーション
メディアバイ上のpush_notification_config と reporting_webhook は、バイを再作成せずに update_media_buy を通じて更新してもよい(MAY)。よくある理由: レシーバー URL のローテーション、期限切れ bearer トークンの置換、署名鍵のスワップ。
セラーは、更新が確認応答された後の次の発火で更新された設定を尊重しなければなりません(MUST)。正式なハンドオフウィンドウはありません — バイヤーは伝播ウィンドウ中に以前の URL に対して少数の発火を受け取ることがあり(MAY)、以前の URL が合体ウィンドウの間静かになるまで両方の URL をライブとして扱うべきです(SHOULD)。
認証更新
永続 Webhook は bearer トークンより長く続きます。bearer 認証(レガシー HMAC プロファイルまたはトークンベース mTLS)を使うレシーバーは、期限切れ前にupdate_media_buy を通じてトークンをローテーションすべきです(SHOULD)。9421 署名プロファイルを使うレシーバーはトークンローテーションを必要としません — 検証はセラーが公開する JWKS に対して行われ、セラーはそれを独立してローテーションします。
セラーの発火がレシーバーから 401 を受け取った場合、セラーはこれを一時的なレシーバー側の設定エラーとして扱うべきです(SHOULD): 標準スケジュールでリトライし、デバッグのため webhook_activity[] に失敗を表面化し、Webhook を自動無効化しない。
終了
永続 Webhook はバイの終端ライフサイクル遷移を通じて発火します:finalデリバリーレポートは、バイがcompleted、canceled、rejectedに達した後に発火する。- 保留中の
impairmentイベントは、セラーがキューに持っている場合、終了前に発火する(または合体されて発火する)。 - 最終発火の後、設定された URL に対してそれ以上のイベントは発火しない。セラーは、バイヤーが終了シーケンスを監査できるよう、終了後の保持ウィンドウの間
webhook_activity[]を保持してもよい(MAY)。
次のステップ
- Task Lifecycle — ステータス値と遷移
- Async Operations — 長時間実行タスクの処理
- Error Handling — Webhook エラーパターン