Skip to main content

スナップショットとログ

AdCP が公開するすべての状態サーフェスは 2 つの顔を持ちます: get_* タスクから読まれるスナップショットと、登録された webhook URL に対して発火するプッシュイベントのログです。スナップショットは今何が真かを言います。ログは何が、いつ、どの id で発火したかを言います。このページは、それらを整合的に保つコントラクトです。 AdCP タスクを呼び出すためにこのページを読む必要はありません。webhook レシーバーを構築する、新しい通知タイプを提案する、またはイベント欠落シナリオがバイヤー側のバグではなく仕様のギャップだと論じるためには、読む必要があります。

The two faces

Snapshot

読み取り API に公開される現在の真実:
  • get_media_buys は各バイの statushealth、オープンな impairments[]webhook_activity[] を返す。
  • list_creatives は各クリエイティブの status を返す。
  • sync_audiences(変更なし)は各オーディエンスの現在の status を返す。
  • get_event_source_health は各ソースの現在の assessment-status を返す。
スナップショットは常に再読み取り可能です。履歴を運びません — 読み取りの瞬間に真であるものだけです。

Log

バイヤーの登録された webhook URL に発火するプッシュイベントのストリーム:
  • 配信レポートの発火(notification_type: scheduled | final | delayed | adjusted)。
  • 依存関係の障害の発火(notification_type: impairment)。
  • 将来のイベントタイプは同じ方法で追加される: 新しい notification-type 値、定義されたペイロード、同じ配信コントラクト。
各イベントは安定した notification_id を運び、スナップショット上で可視の変更に対応します。

The five rules

これらのルールは、プロトコル内のすべてのスナップショット/ログペアにわたって適用されます。新しい通知タイプを構築しているなら、その設計は 5 つすべてを満たさなければなりません。

1. Two distinct ids: per-fire and per-state

トランスポートのリトライは idempotency_key で重複排除する。発火を状態に相関させるのは notification_id で行う。 これらは同じ発火上の異なる id です — レシーバーは両方を追跡しなければなりません(MUST)。
  • idempotency_key — トランスポート層、配信試行ごと。各発火についてセラーが発行する。レシーバーはこれで重複排除し、同じ論理的発火のリトライを抑制する。webhooks トランスポートコントラクト で定義。
  • notification_id — イベント層、状態イベントごと。同じ論理的イベントの再発火をまたいで安定。状態形状のイベントについては、これはリソースの安定 id に等しい(例: impairment_id は障害イベントの notification_id)。mcp-webhook-payload.json でエンベロープレベルに型付けされ、タイプごとの投入は notification-type.json の enumDescriptions で文書化される。永続的な状態 id を持たないポイントインタイムのデータイベント(例: 配信レポート発火)では欠落する。
分割は意図的です。同じ idempotency_key を 2 回見るレシーバーはトランスポートのリトライを観測しています — 興味を引かない、重複排除して先に進む。異なる idempotency_key の下で同じ notification_id を 2 回見るレシーバーは再発火を観測しています — シグナルです。セラーは繰り返しています。通常は、バイヤーのレシーバーが十分に長く到達不能で、セラーが状態が配信されたことを確認したいからです。それはレシーバーが折り畳むべきでない、イベント欠落の警告です。 状態形状のイベント(障害、ライフサイクル)については、状態ごとの id はリソース id です。ポイントインタイムのデータイベント(配信レポート発火)については、永続的な状態 id はありません — 発火ごとの idempotency_key がすべてです。その非対称性は、下のルール 4 の限界について正直です。

2. Every push event corresponds to a snapshot delta

webhook 専用の状態はありません。webhook が notification_type: impairment で発火すると、影響を受けたメディアバイの impairments[] は次の読み取りでその障害を示します。配信レポートが発火すると、次の get_media_buy_delivery は同じレポートウィンドウを反映します。プッシュチャネルは、読み取り API から利用できない情報を運びません。 このルールは、対応する読み取り可能な状態なしに一時的なシグナルとしてのみ存在するプッシュイベント — 「X を知りたいかもしれない」 — を排除します。状態を変えない提案をサーフェスしたいなら、webhook ではなくプルツールを構築してください。

3. Push is at-least-once; the snapshot is authoritative

プッシュとスナップショットが不一致のとき、スナップショットが勝ちます。重複した webhook 発火(同じ notification_id)は、at-least-once 配信下で期待される動作です — バイヤーエージェントは重複排除して続けます。古い webhook 発火(リソースが先に進んだため、プッシュがスナップショットがもはや反映しない状態をレポートする)も期待されます — バイヤーエージェントはプッシュペイロードに基づいて行動するのではなく、スナップショットを再読み取りします。 これが、レシーバーがプッシュ上で不可逆な行動を取る前にスナップショットに対して検証しなければならない(MUST)理由です。

4. Either path is complete

webhook を使うバイヤーは確実にすべてのデータを得ます。GET のみを使う(webhook なし)バイヤーは同じデータを得ます。2 つの経路は内容と粒度において同等です。バイヤーはレイテンシー、人間工学、レシーバーインフラに基づいて選びます。 このルールには 2 つの半分があります:
  • 状態イベント(障害、ライフサイクル、ステータス変更)について: GET は現在の状態を返す。webhook を逃したバイヤーは get_* を呼んでスナップショットを読む — リカバリーは無損失。✅ 今日成立。
  • データを運ぶイベント(配信レポート発火、個別のログイベント)について: GET は、セラーが reporting_capabilities.windowed_pull_granularities で宣言するすべての粒度でウィンドウ付きプルを尊重しなければならず(MUST)、その粒度で webhook が配信するのと同じウィンドウ化で行う。["hourly", "daily"] を宣言するセラーは、get_media_buy_delivery で時間別と日別のウィンドウ付きプルを尊重しなければならない(MUST)(time_granularity + include_window_breakdown: true 経由)。スライスペイロードは、それが置き換えられたであろう webhook 発火と形状整合します。セラーは、プルに公開するより高頻度の webhook を発してもよい(MAY) — ストリームタップアーキテクチャで一般的で、webhook が Kafka タップで、履歴読み取りがより粗い粒度のウェアハウスを通る場合です。その場合、バイヤーはケイパビリティを通じて事前に、より高頻度ではプルリカバリーが利用できないことを知り、それについては webhook をプライマリとして扱います。
2 経路等価のコントラクトは、各セラーの宣言された同等集合内で成立します。セラーは集合について正直でなければなりません(MUST): GET サーフェスが実際に webhook ペイロードを再現できるすべての粒度を宣言する、それ以上でもそれ以下でもなく。ある粒度を宣言するがその粒度でのプルを拒否するセラーはルール 4 に違反しています。ある粒度を省略するセラーはその頻度で 2 経路同等をオプトアウトしており、問題ありません。宣言された集合外のプルは、ケイパビリティをエコーする error.details.supported_granularities を伴う UNSUPPORTED_GRANULARITY を返します。 2 経路等価がなければ、AdCP は一部のチャネルでは pub/sub、他ではREST になります — コントラクトに対して構築するバイヤーは、どのモデルがどこに適用されるかを知らなければなりません。それがあれば、両経路は等価です: バイヤーはレイテンシーのために webhook を、シンプルさのためにポーリングを選び、どちらの経路でも同じデータを得ます。

5. Push events and log entries share an id space

webhook_activity[] を通じてサーフェスされる webhook 配信は、バイヤーがプッシュボディで受け取ったのと同じ notification_id を参照します。バイヤーは「私は発火 X を受け取った」を「セラーのログは発火 X を示す」と、2 つの名前空間をまたいだ帳簿付けなしに相関できます。同様に、impairments[] で参照される impairment_id は、それを告知したプッシュの notification_id と一致します。

Webhook activity log pattern

ルール 5 のトランスポートの半分。スナップショット読み取り API を公開し、それに関連する webhook 発火を持つ任意の AdCP リソースは、その読み取り API 上に webhook_activity[] 配列もサーフェスしてもよい(MAY) — 呼び出し元プリンシパルにスコープされた、最近の発火ごとのトランスポートレコードで、発火が着地しなかったときやリトライの軌跡が怪しく見えるときのバイヤー側デバッグに有用です。このセクションは、そのサーフェスを採用する任意のリソースが従わなければならない(MUST)コントラクトです。

Canonical record shape

レコード形状は /schemas/core/webhook-activity-record.json で固定されています。このサーフェスを採用する読み取りスキーマは、それをインライン化するのではなく正準レコードを $ref しなければなりません(MUST) — 形状は意図的にリソースをまたいで一様であり、バイヤーのデバッグツールがリソース固有のパースなしに任意の読み取り API から webhook_activity[] を消費できます。 各レコードは、idempotency_key(ルール 5 によりペイロードの idempotency_key に等しい — 並行する delivery_id はない)、subscriber_id(#3009 マルチサブスクライバー用に予約)、fired_atcompleted_atnotification_typesequence_numberattempt(1 始まり、試行ごとに 1 レコード)、statussuccess / failed / timeout / connection_error / pending)、url(クエリ文字列とフラグメントを除去、秘密形状のパスセグメントを編集)、http_status_coderesponse_time_mspayload_size_byteserror_message(サーバー側の分類のみ — リクエスト/レスポンスのボディやヘッダーは決して含まない)を運びます。

Request-field convention

webhook_activity[] をサーフェスする読み取りスキーマは、呼び出し元がリソースをまたいで一様にオプトインできるよう、同じ 2 つのリクエストフィールド名を使わなければなりません(MUST):
  • include_webhook_activity — boolean、デフォルト false。true のとき、セラーは各アイテムに webhook_activity[] 配列を返してもよい(MAY)(下記の 3 状態存在セマンティクスに従う)。
  • webhook_activity_limit — integer、範囲 1–200、デフォルト 50。返されるレコードのアイテムごとの上限、最新順。

Scoping (normative)

webhook_activity[]呼び出し元プリンシパルにスコープされなければなりません(MUST)。複数のプリンシパルがアカウントレベルのアクセスを通じて同じリソースへの可視性を共有するとき、各プリンシパルは自身の登録エンドポイントをターゲットにする発火のみを見ます。これはプッシュ配信自体に適用されるのと同じスコーピングルールです。

Retention (normative)

webhook_activity[] をサーフェスするセラーは、各レコードの completed_at から少なくとも 30 日間レコードを保持しなければなりません(MUST)。これはすべての終端ステータスに一様に適用されます — successfailedtimeoutconnection_error はすべて completed_at を投入し(timeoutconnection_error については、セラーが試行を終端と宣言した瞬間)、30 日の時計はそこから走ります。まだ pending ステータスのレコード(試行が飛行中またはリトライキューイング中、completed_at は null)については、時計は試行が終端になるまで fired_at から走り、その後 completed_at から 30 日に遷移します — したがってリトライの軌跡は、最初の発火が 29 日前に起こったという理由だけで飛行中に期限切れになりません。 30 日の下限はハードコントラクトです — それを尊重できないセラーは、より短いウィンドウを返すのではなくフィールドを完全に省略しなければなりません(MUST)(下記の 3 状態存在を参照)。これはバイヤーに、デバッグツールを構築できる単一の保持保証を与え、薄いストレージのセラーには、仕様がセラーごとの保持下限を交渉することを強いるのではなく、3 状態セマンティクスによるクリーンなオプトアウトを与えます。

Three-state presence semantics

セラーはこれらを単一の状態に折り畳んではなりません(MUST NOT)。include_webhook_activity: true によるオプトインは、セラーの本質的なケイパビリティを上書きしません — 保持下限を満たせないセラーは、リクエストにかかわらず省略を返します。 予期しない省略を診断するバイヤーは、オペレーターの助けを必要とせずに原因を判別する、容易に観測可能な 2 つのシグナルを持ちます: (1) リソースについての自身の push_notification_config 登録状態(「登録エンドポイントなし」を除外)、(2) セラーのケイパビリティ宣言(「ケイパビリティサーフェスがチャネルを除外」を除外)。両方が確認できたとき、「セラーが発火履歴を永続化しない」が残る原因であり、それ以上のプロトコル側の修正は利用できません — エスカレートしてください。

Record cardinality

試行ごとに 1 レコード。成功した初回試行の発火は、attempt: 1 の単一レコードとして現れます。3 試行のリトライ軌跡(例: 2 回失敗して 1 回成功)は、idempotency_key を共有する 3 レコードとして現れます — 軌跡は、バイヤーがそのキーでレコードをグループ化して再構成します。

Privacy

  • url はクエリ文字列とフラグメントを除去しなければならず(MUST)、高エントロピー / トークン形状のパスセグメントはさらに編集すべきです(SHOULD)。
  • error_message はサーバー側の分類文字列のみです — リクエストヘッダー、レスポンスボディ、バイヤーエンドポイントのスタックトレースは決して含みません。
  • リクエストとレスポンスのボディは基本サーフェスの範囲外です。将来の include_webhook_payloads 拡張が、より厳格なアクセス制御の下でそれらを追加するかもしれず、ボディが設定された上限を超えるとき /schemas/core/truncation-sentinel.jsonユニバーサル切り詰めセンチネル を使うでしょう。

Adoption checklist

webhook_activity[] を採用するリソースは、次のすべてを満たさなければなりません(MUST)。リストは「MUST」フックが曖昧でないよう意図的に明示的です。このリストにないものはすべて採用者の裁量です(例: 1–200 範囲内のリソースごとのカーディナリティ調整)。
  1. 通知チャネル(前提条件)。 採用には該当する発火タイプの登録された通知チャネルが必要。メディアバイは今日、バイごとの push_notification_config(および関連する reporting_webhook)によってこれを満たす。任意の単一バイより長生きするリソース — クリエイティブ、オーディエンス、プロパティ、アカウントレベルのガバナンス — は、#4582 track 3 で定義されるアカウントごとのサブスクリプションモデル(3.2.0 で予定)を待つ。2 つは同じ前提条件を満たす異なるプリミティブ: バイにアタッチされたバイスコープの設定 blob と、アカウントスコープのサブスクリプションリソース。チャネルなしには webhook_activity[] がログする発火はなく、この項目は下の他のすべてのルールをゲートする。採用者は呼び出し元ドキュメントで特定のチャネルを引用しなければならない(MUST)。
  2. レコード形状。 アイテムスキーマは /schemas/core/webhook-activity-record.json$ref しなければならない(MUST)。リソース固有の相互参照(例: レコードがアカウントレベルの読み取り内にネストされるときの親リソース id)は、トップレベルのレコードフィールドとしてではなく、正準レコードの ext エンベロープに置く。
  3. リクエストフィールド。 オプトインフィールド名は include_webhook_activity(boolean、デフォルト false)と webhook_activity_limit(integer、1–200、デフォルト 50)でなければならない(MUST)。200 の上限は正準の上限。採用者はリソースごとに最大値を狭めてもよい(MAY)が、200 を超えたりフィールドをリネームしたりしてはならない(MUST NOT)。
  4. スコーピング。 上記 § Scoping に従い、呼び出し元プリンシパルのみでなければならない(MUST)。
  5. 保持下限。 上記 § Retention に従い、30 日の下限を尊重しなければならない(MUST)。ピボット(completed_atpending の除外付き)はリソース間で同じ。
  6. 3 状態存在カーディナリティ。 省略 / [] / 非空が 3 状態。採用者はそれらを折り畳んではならない(MUST NOT)。
  7. ケイパビリティゲート。 採用者は、どのリソース固有のケイパビリティ宣言がフィールドをゲートするかを文書化しなければならない(MUST)(メディアバイについては webhook を含む capabilities.media_buy.propagation_surfaces)。「フィールド省略」状態の特定の原因はリソース固有であり、採用者は呼び出し元ドキュメントでそれらを列挙しなければならない(MUST)。カーディナリティと、省略が「発火は起こらなかった」ではないというルールは普遍的。
  8. 通知タイプレジストリ。 webhook 発火が /schemas/enums/notification-type.json にない通知タイプを運ぶ採用者は、正準レコードに並行 enum を鋳造するのではなく、それらのタイプをその共有 enum に追加しなければならない(MUST)。enum はクロスリソースレジストリ。

Consumers and the dependency chain

Today (3.1)

  • get_media_buys.media_buys[].webhook_activity[] — このパターンの最初で現在唯一のコンシューマー。通知チャネルは既存のバイごとの push_notification_config なので、チェックリストの項目 1 は新しいプリミティブなしで満たされます。ケイパビリティゲート: フィールドがバイにサーフェスされるには capabilities.media_buy.propagation_surfaceswebhook を含まなければならない。呼び出し元ドキュメントについては get_media_buys § Webhook activity を、このサーフェスがデバッグするトランスポート側のルールについては 永続的 webhook コントラクト を参照。

Account-level adopters (3.1)

単一のメディアバイより長生きするリソースは、プッシュチャネルを任意の 1 つのバイではなくアカウントに登録します。アカウントレベルのサーフェスは notification_configs[] です — sync_accounts に運ばれ list_accounts にエコーされる、サブスクライバーごとの登録の配列。各エントリは event_types[] でフィルタリングするため、サブスクライバーは自身のエンドポイントが扱うタイプのみを受け取り、異なる subscriber_id を持つ複数のエントリが単一のイベントを複数のエンドポイントにファンアウトします(マルチサブスクライバー合成)。
  • #2261 クリエイティブライフサイクル webhooklist_creatives.creatives[].webhook_activity[] はこのパターンの 2 番目のコンシューマー。通知チャネルはアカウントの notification_configs[] 集合で、プロビジョニングまたは設定更新モードで sync_accounts 経由で登録される。サポートされるイベントタイプとタイプごとの合体ウィンドウは get_adcp_capabilities 経由で宣言される。2 つのクリエイティブライフサイクルイベントタイプ — creative.status_changedcreative.purged — はメディアバイ webhook アクティビティと同じレコード形状と保持ルールを共有する。親クリエイティブは曖昧でないので、内部レコードで ext.creative_id は省略してもよい(MAY)。呼び出し元ドキュメントについては list_creatives § Webhook activity を参照。
  • バイより長生きする他のリソース#1711 の下のオーディエンス、プロパティ、アカウントレベルのコンプライアンス — は同じチェーンに従う: sync_accounts.accounts[].notification_configs[] 経由でサブスクライブし、リソースの list_ タスクで webhook_activity[] 読み取りを採用する。これらはオープンな RFC。
ハードパージのためのルール 4 の除外。 purge_kind: hard を伴う creative.purged(法的消去のみ — GDPR 第 17 条、CCPA 削除、裁判所命令)は、ルール 4 の唯一の認可された例外です: webhook 発火に対応するスナップショットデルタがない、なぜならセラーはトゥームストーンを保持してはならない(MUST NOT)からです。ハードパージ発火を逃したバイヤーは読み取り側のリカバリーを持ちません。それはプロトコルのギャップではなく、法制度の設計上の制約です。ソフトパージは list_creativesinclude_purged: true 付き)にトゥームストーンを保持し、ルール 4 準拠のままです。 採用者は、通知チャネルがバイごとかアカウントごとかにかかわらず、このチェックリストにそのまま従います。

What this rules out

  • 状態を変えない提案のためのプッシュチャネル。 「セラーがあなたに X を知ってほしい」が読み取り可能なフィールドに対応しないなら、それはスナップショット/ログイベントではありません。代わりにプルツールを構築してください。(advisory epic を参照。)
  • 過去の webhook を再発火するリプレイツール。 スナップショット読み取りがリプレイです。リプレイツールはオペレーター側のデバッグ機能であり、バイヤー向けのプロトコルコントラクトの一部ではありません。
  • バイごとのプッシュでのイベントごとのサブスクリプションフィルタリング。 メディアバイに push_notification_config を登録するバイヤーは、そのバイに対して発火するすべてのイベントタイプを受け取ります。レシーバーでのフィルタリングは問題ありません。バイごとのプロトコルサーフェスでのフィルタリングは範囲外です。アカウントレベルのサブスクリプション(notification_configs[])は例外です — それらは登録時に event_types でフィルタリングします。なぜならアカウントレベルのサーフェスは異種(クリエイティブイベント、将来のオーディエンス/プロパティイベント)で、クリエイティブイベントのみを扱うエンドポイントは、そうでなければ解釈できないシグナルを強制的に供給されるからです。
  • 「私の webhook を受け取りましたか?」の確認ステップ。 レシーバーは HTTP 2xx で確認します。送信者は 永続的 webhook コントラクト に従って非 2xx でリトライします。セラーは受領のためにバイヤーをポーリングしません。

Where the surface doesn’t yet follow this

  • 配信レポートscheduled / final / delayed / adjusted)はこのコントラクトに先行します。ルール 4 は 3.1 で 2 つのサーフェスを通じてそれらについて閉じます:
    • ウィンドウごとのデータ同等get_media_buy_deliverytime_granularity + include_window_breakdown: true を受け入れ、同じ粒度で reporting_webhook ペイロードと形状整合する media_buy_deliveries[].windows[] スライスを返す。reporting_capabilities.windowed_pull_granularities 経由でケイパビリティスコープ。宣言された集合外のプルは UNSUPPORTED_GRANULARITY を返す。#4590 で着地。
    • 発火ごとのトランスポートログ — ウィンドウごとの同等があっても、webhook 配信をデバッグするバイヤーは、どの発火がいつ自身のエンドポイントに当たったかを見たい。get_media_buyswebhook_activity[] サーフェス(#4278)がトランスポート層の可観測性についてこれを閉じる。それは上記の webhook activity log pattern の最初のコンシューマー。パターンを採用する将来のリソースは、同じレコード形状、保持下限、3 状態存在セマンティクスに従う。
  • オーディエンスとプロパティのライフサイクル webhook — クリエイティブライフサイクル webhook は今や #2261(アカウントレベルの notification_configs[] + list_creatives.webhook_activity[])経由でこのパターンを採用します。バイのスコープ外のオーディエンス停止とプロパティの公開停止はオープンなままです — それらが着地するまで、スナップショットの半分(新しい sync_audiences またはプロパティクロール)が、アクティブなバイに現在参照されていないときのそれらのリソースへの変更の唯一の信頼できるシグナルです。

When you’d be right to push back

このセクションは非規範的です。例外を上げることが妥当なときを記述するもので、認可されるときを記述するものではありません。
ユースケースが、スナップショットの半分を持たないイベントを本当に必要とするとき — ポーリングコストが支配的でリカバリーが重要でない高頻度シグナル(例: メトリクスストリーム)。AdCP は今日そのようなものを持ちません。それを提案しているなら、明示的に名指しし、なぜスナップショット経由のプルが適合しないかを論じてください。レビュアーはそれを、このページがコミットするコントラクトと秤にかけます。