feat(webhooks): creative lifecycle webhooks + sync_accounts universal write (#2261, #4582 track 3)#4730
Merged
Merged
Conversation
…sal account-state write (closes #2261, lands #4582 track 3) Account-anchored creative lifecycle webhooks (creative.status_changed, creative.purged) with sync_accounts as the universal write surface for both implicit (provisioning) and explicit (settings-update via AccountRef) accounts. Governance unchanged — no implicit subscription; governance teams that want webhooks register an explicit notification_configs[] entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y-record.subscriber_id required-vs-optional rule Protocol reviewer flagged that UNSUPPORTED_PROVISIONING is referenced from sync_accounts-request but not in error-code.json (enum + enumDescriptions + enumMetadata). Also tightened webhook-activity-record.subscriber_id prose to state it's required on account-anchored channels (where every subscriber has a subscriber_id at registration) and optional on per-resource push channels (single-subscriber unambiguous, multi-subscriber lands with #3009). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New code added in 92199a2 needs a drift-lint disposition. Held for 3.1 with target_version="3.1" — consistent with the other sync_accounts wire additions (BILLING_NOT_SUPPORTED, BILLING_NOT_PERMITTED_FOR_AGENT, PAYMENT_TERMS_NOT_SUPPORTED). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The provisioning/settings-update oneOf is structurally disambiguated (presence/absence of `account` AccountRef vs flat `brand+operator+billing` trio, enforced by mutual `not: { required: [...] }` constraints). Adding a tagged `mode` discriminator would be redundant. Classified as `narrowable` per adcp#3917 — same pattern as the existing sync-accounts-response and sync-governance-response oneOfs already in the baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #2261. Lands #4582 track 3 (per-account subscription model) without adding a new tool —
sync_accountsbecomes the universal account-state write surface for both implicit (provisioning) and explicit (settings-update via AccountRef) accounts. Governance unchanged: no implicit subscription; governance teams that want webhooks register an explicitnotification_configs[]entry.Three expert review passes (
ad-tech-protocol-expert-deep,adtech-product-expert-deep,code-reviewer-deep,docs-expert-deep) applied to the prior shape; this PR is the result of converging on option 3 after the experts flagged auto-subscription as a foot-gun and naming as misleading on the earliersync_governance-overload design.Events
creative.status_changed— seller/system-initiated transitions only. Buyer-initiated transitions (archive, unarchive, resubmit) ack via thesync_creativesresponse path; they do NOT fire.transition.fromnarrowed to{processing, pending_review, approved}— post-terminal states never appear.creative.purged— seller destruction.purge_kind: softretains a tombstone onlist_creativesfor 30 days;purge_kind: hardretains nothing (sanctioned Rule 4 carve-out for legal erasure). No coalescence.Paired with #4588
media-buy.impairment(no ordering guarantee; reconcile via snapshot).Subscription model —
sync_accountspolymorphic keyEach per-account entry uses one of two key shapes (
oneOf):brand+operator+billing(today's shape). Implicit-account sellers provision/upsert.account(AccountRef) keyed byaccount_id(explicit) or natural key (implicit). No provisioning side effects. Explicit-account sellers (DV360-class) gain a single focused write surface; sellers that don't implement either mode reject withUNSUPPORTED_PROVISIONING.Both modes carry
notification_configs[](replace semantics, max 16,subscriber_idalways required,additionalProperties: false). Sellers MUST rejectevent_types[]containing any media-buy-anchored type (forward rule, not enumeration).list_accountsechoesnotification_configs[]per account with credentials redacted (write-only).Read surface (#4701 track 4 adoption)
list_creativesadopts the 7-itemwebhook_activity[]checklist:include_purged: truereturns soft-purged tombstones via a wrappedpurge: { kind, at, reason_code }block — co-presence enforced by schema, not prose.statusis frozen pre-purge.include_webhook_activity: true+webhook_activity_limit(1–200) return per-creativewebhook_activity[]records.$refs the canonicalwebhook-activity-recordfrom feat(webhooks): webhook_activity[] on get_media_buys + standardized log surface (#4278, #4582 track 4) #4701;notification_typediscriminates status changes vs purges.Reason codes
New
creative-event-reason-codeenum — 13 categorical values, distinct fromimpairment-reason-code. Includesreview_passed(initial approval),advertiser_request(buyer-initiated takedown),account_closed/account_suspended(cascade), foldedseller_archive(wasinactivity_archive+storage_policy), and pinnedretention_expired(soft only) /legal_erasure(hard only). Each value's enumDescription documents buyer remediation.Expert-flagged fixes applied
sync_accountsis the right surface name)review_passedaddedsubscriber_idalways required (no conditional required-when-multiple)additionalProperties: falseonnotification-config.jsontransition.fromnarrowed to non-terminal statespolicy_revocationvscontent_driftwhen-in-doubt)retention_expired↔ soft only,legal_erasure↔ hard onlysnapshot-and-log.mdxevent_typesphrased as forward rulecreative.status_changedandmedia-buy.impairmentmaxItems: 16rationale documentedTest plan
npm run build:schemas— cleannpm run test:schemas— 7/7npm run test:examples— 36/36npm run test:composed— 43/43npm run test:json-schema— 260/260npm run test:docs-nav— 15/15npm run check:registry— 14 entries OKnpm run typecheck— clean@adcp/sdkand verifyNotificationConfig,CreativeStatusChangedWebhook,CreativePurgedWebhooktypes export as expected🤖 Generated with Claude Code