Skip to content

RFC: Webhook foundation for AdCP (epic) #4582

@bokelley

Description

@bokelley

Problem

AdCP webhooks are specced per feature. sync_creatives task completion, reporting_webhook on media buys, mcp-webhook-payload, push_notification_config on async operations, and proposed channels for creative lifecycle (#2261), dependency impact (#2853), and org-level compliance alerts (#1711) each define their own delivery semantics, subscription model, and observability. Each new RFC reinvents the contract. There is no shared log surface, no story for resources that outlive a single media buy, and no canonical contract for what a buyer agent can assume about a persistent push channel.

The result: every new webhook RFC re-litigates at-least-once delivery, coalescence, replay, auth renewal, and the snapshot-vs-log relationship. #4278 (delivery visibility) and #2853 (dependency impact) are both currently drafting the same primitives in parallel.

Goal

One contract for persistent push. One shape for the snapshot+log duality. One subscription model spanning per-resource and per-account scope. Future RFCs reference the foundation instead of re-deriving it.

Design principles

  • Snapshot is authoritative. The current truth lives on the read API; the webhook is a change signal, not the source of truth.
  • Push is at-least-once and unordered. Buyers reconcile via the snapshot, not via webhook ordering. Idempotency via stable notification_id.
  • Every push event has a snapshot delta. A webhook fires when a state visible on a read API changes. No webhook-only state.
  • Subscription anchors to the resource that outlives the relationship. Media buys carry their own webhook config. Creatives, audiences, properties — which outlive any single buy — anchor at the account.
  • Replay = re-read the snapshot. No event-replay primitive in v1. If you missed a push, ask the read API for current state.
  • One contract across all event types. Same delivery semantics for delivery reporting, dependency impact, creative lifecycle, governance alerts. Differ in payload, not in transport.

Tracks

1. Protocol pattern doc — snapshot/log duality

Foundation document under docs/protocols/. Names the five-rule contract:

  1. Every push event has a stable notification_id.
  2. Every push event corresponds to a snapshot delta visible on a read API.
  3. Push is at-least-once; the snapshot is authoritative when they disagree.
  4. Replay = re-read the snapshot. No event-replay primitive in v1.
  5. Push events and log entries share an id space — a webhook delivery references the same id the buyer received in the push body.

Everything else in this epic references this doc.

2. Persistent webhook contract

Codify the on-the-wire delivery contract:

  • At-least-once delivery; idempotency via notification_id.
  • No ordering guarantee.
  • Per-kind coalescence windows (acute / batched).
  • Mutability of push_notification_config via update_* (without re-creating the resource).
  • Termination — webhooks keep firing through terminal lifecycle states (e.g., final reporting), then stop.
  • Auth renewal — persistent webhooks outlive bearer tokens; renewal story needed.

3. Subscription model

Two subscription anchors:

Both fire against the same webhook URL shape. Differ only in subscription anchor and event scoping.

4. Standardized log surface

Generalize #4278's webhook_activity[] from a media-buy-specific field to a pattern: any resource with a snapshot exposes recent events on read. Includes:

5. Auth & transport hygiene

Cluster of existing open issues that converge here:

6. Delivery semantics edge cases

7. Conformance

Out of scope (consumers of this epic, separate tracks)

These epics depend on this one; they don't define their own contracts.

Release strategy

Target 3.1.0 for tracks 1–4 (snapshot/log doc, persistent contract, subscription model, log surface). All three currently-active consumer RFCs (#2853, #2261, #4278) need these primitives in 3.1.

Track 5 (auth & transport hygiene) lands incrementally — some items are spec clarifications (#3554, #3555, #3557, #4339), others are migration tracks (#4205) with their own cadence. #4288 is a 4.0 breaking change.

Track 6 (#3245) and track 7 (#2618) follow the rest.

Open questions

  1. Per-account subscription anchor — where does it live in the schema? Account-level notification_configs[] array? Per-resource-type subscription endpoints on the account? Whatever shape, it needs to compose with 4.0: multi-subscriber reporting_webhook on media buys #3009's multi-subscriber direction.

  2. Replay primitive — really out of scope for v1? Catch-up via snapshot works for current-state surfaces, but not for events that don't change current state (e.g., a delivery report fire that already drained from the log). Worth confirming the constraint or scoping in a minimal list_notifications(resource_id, since) tool.

  3. Stable id ownership. Who issues notification_id? Seller agent generates and includes in the push body; buyer dedupes by it. Trivial in single-subscriber case; gets interesting with 4.0: multi-subscriber reporting_webhook on media buys #3009 multi-subscriber (does each subscriber see the same id? probably yes — same event, same id).

  4. Retention SHOULD vs. MUST. Inherited from RFC: Buyer-side webhook delivery visibility on get_media_buys #4278. The foundation should pick a normative stance so consumers don't each re-decide.

  5. Snapshot/log doc location. New file under docs/protocols/ vs. expansion of existing docs/building/implementation/webhooks.mdx. Probably the former — the existing doc is implementation-oriented; the new one is contract-oriented.

Consumers to cross-link

Metadata

Metadata

Assignees

No one assigned

    Labels

    epicMajor deliverable — auto-adds to roadmap boardrfcProtocol change — auto-adds to roadmap board

    Type

    No type

    Projects

    Status

    No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions