Skip to main content
AdCP operations can be observed through more than one channel. A direct task response, an AdCP task-status read, an A2A transport Task, a continuation, and a webhook can all describe related work, but their identifiers are not interchangeable. This page defines the normative identity, convergence, resumability, and publication rules shared by every AdCP task. Task-specific documentation can narrow these rules, but MUST NOT weaken them.

Identity model

Implementations MUST retain each identity in its own namespace and MUST NOT substitute one identity for another. An SDK MAY present a composite operation object containing several of these values. That convenience object does not merge their semantics.

Stable seller-work identity

Once an operation is bound to an AdCP task_id, every later AdCP observation of that operation—submitted, subsequent status reads, paused states, terminal results, task webhooks, and reconciliation—MUST repeat that value or inherit it from an envelope whose binding is unambiguous. A different explicit AdCP task_id for the same operation is a protocol conflict. A client MUST NOT silently rebind the operation or treat the new value as a successor. An A2A transport Task can complete with an AdCP Submitted result. In that case, the A2A Task.id identifies the completed transport invocation and the AdCP task_id identifies the continuing seller work. Polling uses only the AdCP handle. Native A2A continuation uses only the A2A Task and context identifiers.

One operation, competing observations

Direct responses, AdCP polling results, continuation responses, and authenticated webhooks are competing observations of one logical operation. They are not independent state machines. An observation is authoritative only after the receiver has:
  1. authenticated its source;
  2. validated its envelope and task-specific payload;
  3. matched the operation, account, task type, and all bound identities; and
  4. verified that the observation is permitted from the operation’s current state.
The first authoritative terminal observation wins. The receiver MUST durably record its terminal identity and canonical payload before publishing terminal business side effects. Later behavior is:
  • an exact terminal duplicate is acknowledged and does not republish effects;
  • a non-terminal observation after terminal settlement is acknowledged as stale and does not reopen the operation; and
  • a different terminal payload or terminal status is a protocol conflict and MUST NOT replace, merge with, or republish over the winner.
completed, failed, canceled, and rejected are terminal. working, submitted, input-required, and auth-required are non-terminal. None can displace an accepted terminal result. unknown is indeterminate and is not an authoritative terminal observation: the receiver MUST reconcile it, and it cannot settle or reopen an operation. A terminal webhook MAY arrive before the originating request returns working or submitted; this is an allowed race, not an unknown-task error. The implementation preserves the validated terminal observation and converges the later response onto it.

Terminal identity and equivalence

For asynchronous work already bound to a seller task, one terminal settlement is identified by (authenticated seller identity, AdCP task_id). task_type, operation, account, and principal bindings are validated constraints on that identity. A mismatch is a correlation conflict, not a second settlement. Synchronous inline terminal responses have no AdCP task identity and cannot race a task webhook: 3.x sellers MUST NOT synthesize a task webhook for an inline terminal response. Channels wrap the task result differently. Before duplicate comparison, a receiver extracts the task-specific terminal value: The canonical comparison record is RFC 8785 JCS of {task_type, status, value}. Transport-only observation fields—including A2A Task and context identifiers, webhook idempotency_key, notification_id, operation_id, AdCP task_id, delivery timestamp, envelope message, and replayed—are not part of value. A field declared by the task-specific response schema remains part of value, including task-body context or errors when declared. When both a terminal result and the polling convenience error are present, the error MUST agree with the canonical fatal error in result. A legacy 3.x poll that exposes only singular error can establish that the task failed, but cannot prove that a later full terminal artifact is an exact duplicate. The receiver preserves the failure fence and reconciles the complete artifact; it does not publish from the incomplete observation or call a different payload equivalent. More generally, when a channel cannot be validated or normalized to the selected task schema, it is not authoritative for publication.

Normative race table

“Acknowledge” below means return the success form appropriate to the channel. For an authenticated HTTP webhook, that is a 2xx response.

Webhook delivery-key binding

A webhook publisher MUST bind (authenticated sender identity, idempotency_key) to the complete authenticated payload’s RFC 8785 JSON Canonicalization Scheme (JCS) form for the advertised retry window. Duplicate object keys MUST be rejected before canonicalization. It reuses that key only for JCS-equivalent retries of that delivery. A re-emission of the same logical notification with a changed envelope or payload uses a fresh delivery key and retains the same notification_id when that event type defines one. A receiver MUST establish the same immutable binding before application side effects. It fingerprints the authenticated canonical payload and stores that fingerprint with the delivery claim. The three outcomes are distinct: The delivery key prevents duplicate transport processing. The stable terminal observation identity prevents duplicate terminal publication across distinct deliveries. Implementations need both protections.

Resumability and continuation generations

input-required and auth-required describe why work is paused. They do not, by themselves, prove that the interaction can be resumed. An SDK or transport exposes a resume operation only when it has a verified continuation descriptor containing the native protocol and transport identity required for that continuation. For A2A, that includes the native A2A Task ID and context ID. An AdCP task_id from get_task_status is only a polling handle and MUST NOT be copied into a missing A2A Task ID or otherwise used to fabricate a continuation. AdCP 3.2 defines no transport-neutral continuation request or generic continuation descriptor in task-body schemas. Application-layer input-required and auth-required responses are therefore resumable only when the active transport independently supplies its native verified resume identity, such as A2A Task.id plus contextId. Standardizing a portable descriptor without a matching redemption method would create an unusable credential; that wire surface is deferred to AdCP 4.0. If a transport did not return enough verified identity to continue, the client fails closed and directs the caller to polling, reconciliation, or a new operation as allowed by the task. A context_id or status label alone does not authorize a resume. Continuation tokens are opaque bearer capabilities. They MUST:
  • contain at least 128 bits of cryptographically random entropy;
  • be bound to the authenticated principal, operation, transport identity, and one continuation generation;
  • be single-generation and expire after a bounded interval;
  • never appear in logs, analytics, URLs, error messages, or telemetry; and
  • be compared and stored using the same protections as other bearer secrets.
When resuming generation A yields generation B, the transition is create-B, make-B discoverable from the operation, then consume A. These are protocol invariants even when an implementation uses more than one durable store. Ordering that merely prevents duplicate dispatch but can strand B after a crash is not recoverable and is non-conformant.

Publication and durable acknowledgement

Deduplication is not complete when a receiver checks a marker and plans to write an acknowledgement later. Before invoking an adopter callback, billing hook, fulfillment handler, or other externally visible side effect, the receiver MUST durably establish a single publication owner for that terminal observation. After successful publication it records durable acknowledgement. Exact duplicates then return success without invoking the handler. If publication fails, ownership can be released or expire under a bounded recovery policy. If a crash makes the external side effect uncertain, the receiver reconciles that side effect or preserves the uncertainty fence; it MUST NOT assume failure and give a second publisher unrestricted ownership.

Retry horizons

An agent’s capability response advertises the request idempotency replay window and, when it emits webhooks, its maximum webhook delivery retry window as webhook_signing.delivery_retry_horizon_seconds. These are guaranteed floors for retained replay evidence, not suggestions to keep retrying until the last second. Receivers retain the immutable webhook delivery-key-to-payload binding and terminal publication proof for at least the greater of 24 hours and the sender’s advertised retry window. When no webhook retry window is advertised by a 3.x peer, both sides use the 3.x compatibility floor of 24 hours. A publisher MUST NOT retry after the window it advertised for the delivery. Retention is measured from the first accepted request or delivery. Extending retention on every retry would allow a hostile or broken peer to keep state alive indefinitely. Clock-skew tolerance cannot shorten the advertised window.

Composite and compatibility operations

Splitting one legacy task into multiple compact tasks changes transaction and failure boundaries. A compatibility facade is lossless only if it preserves the legacy task’s validation and atomic-commit semantics, not merely its final field values. For example, a legacy create_media_buy can include packages, creatives, and assignments in one request. Structural, reference, and routing validation of those inline creatives occurs before commit, so one such validation failure rejects the request without creating the buy. Later asynchronous policy or human review can still reject a creative without rolling back an already valid buy. A facade implemented as compact purchase followed by creative sync moves the pre-commit validation boundary and can commit spend before it discovers a structural or routing failure. That sequence is not a lossless implementation. A compatibility facade therefore either:
  1. validates and commits every component through one private all-or-none boundary; or
  2. rejects before the first mutation.
When a caller deliberately uses the compact tasks as separate operations, partial success is valid but MUST be explicit. Each step has its own authorization, idempotency key, task identity, terminal outcome, and recovery path. Compensation is a new mutation with its own observable result; it MUST NOT be described as rollback of the earlier committed step. See Legacy and compact lifecycle compatibility for the media-buy transaction-boundary matrix.

Implementation checklist

For every asynchronous or compatibility workflow, document and test:
  1. every identity and its issuing authority;
  2. the point at which seller-work identity becomes stable;
  3. the canonical terminal payload and conflict comparison;
  4. publication ownership before external effects and acknowledgement after;
  5. continuation generation, supersession, and crash discovery;
  6. advertised request and webhook retry horizons;
  7. every mutation boundary and possible partial-success state; and
  8. recovery after a crash at each boundary.
Failing closed prevents an unsafe duplicate. Conformance also requires a durable route to discover and complete legitimate committed work.