feat: AdCP 3.0 webhook support (signing + idempotency)#64
Merged
Conversation
Implements publisher + subscriber helpers for the two AdCP 3.0 webhook
baselines:
- PR #2417: idempotency_key required on every webhook payload so receivers
have a single canonical dedup field.
- PR #2423: RFC 9421 webhook-signing profile (adcp/webhook-signing/v1)
with distinct tag + adcp_use from request-signing, content-digest
required.
adcp/signing: parameterize existing RFC 9421 implementation with a Profile
struct (Tag + AdcpUse + ErrorPrefix). Zero-value Profile preserves
back-compat as ProfileRequestSigning. ProfileWebhookSigning enables the
webhook variant without forking the signer. Error.WireCode(profile) emits
the profile-specific prefix in WWW-Authenticate so receivers see
webhook_signature_* vs request_signature_*.
adcp/webhook: new package. Marshal generates UUIDv4 keys and freezes
wire bytes. Deliver is the one-shot signed POST. Publisher adds retry
with exponential backoff, Retry-After, bounded by MaxAttempts + MaxElapsed.
HTTPHandler composes signature verification (DigestRequired) with
idempotency-based dedup scoped per authenticated sender. DecodeConfig
extracts the subscriber URL from push_notification_config.
Safety gates: HTTPHandler panics when Verification is nil unless
AllowUnverified is explicitly set (prevents the "custom Sender + no
verification = attacker-controlled dedup scope" misconfiguration).
NewSigner force-overrides Profile so webhook signers cannot emit
request-signing signatures.
Auto-generation: IdempotencyKeyPtr methods on each webhook payload type
are emitted by generate.py alongside the struct. Adding a 6th webhook
schema to WEBHOOK_SCHEMAS automatically extends the set of types that
implement webhook.Payload.
Test coverage: 25 webhook tests including end-to-end HTTP round-trips,
cross-profile rejection, content-digest enforcement, dedup scope
disjoint from request-side, retry classification, Retry-After parsing,
MaxElapsed + context cancellation, panic gates, and the webhook-signing
wire error prefix.
Reviewed by code-reviewer, security-reviewer, ad-tech-protocol-expert,
dx-expert. All HIGH findings addressed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t from 6 existing skills Canonical source for emitting AdCP 3.0 webhooks: signer setup, DecodeConfig usage (with the three-case return table), Publisher-vs-Deliver decision, 5 gotchas (mutation, CheckRedirect, re-marshal on retry, profile separation, key persistence), retry tuning, and a Common Mistakes error→fix table. Each of the 6 existing agent-builder skills (build-seller-agent, build-generative-seller-agent, build-retail-media-agent, build-collection-agent, build-signals-agent, build-creative-agent) now points to build-webhook-publisher from its async / notification section, so updates to webhook guidance propagate automatically. Validated by a generation cycle: a coding agent given only these skills and the task "build a seller that emits task-status webhooks" produced code that compiled on first pass and hit zero of the five flagged footguns. Gen-cycle code was kept under .context/ (gitignored). The Quickstart code block is compile-checked by the existing TestSkillSkeletonsCompile in adcp/skill_compile_test.go. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts: # adcp/schemas/.bundle-sha256
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
Implements publisher + subscriber helpers for the two AdCP 3.0 webhook baselines:
idempotency_keyrequired on every webhook payload. Receivers dedupe on this single canonical field scoped per authenticated sender.adcp/webhook-signing/v1profile. Distinct tag +adcp_usefrom request-signing, content-digest REQUIRED.Adds a new
adcp/webhookpackage plus a newskills/build-webhook-publisherskill that the 6 agent-builder skills cross-reference. All existing signing/idempotency tests continue to pass — zero-valueProfilepreserves back-compat.What's new
Core (
adcp/signing,adcp/webhook)signing.Profile—{Tag, AdcpUse, ErrorPrefix}bundle. Exported varsProfileRequestSigning(default) andProfileWebhookSigning.signing.GenerateKeyForProfile— generates JWKs scoped for either profile viaadcp_use.Error.WireCode(profile)— emitswebhook_signature_*vsrequest_signature_*inWWW-Authenticatebased on profile; Go-levelErrorCodeconstants stay stable.webhook.Marshal/Deliver/Publisher— sender primitives + production retry layer.Publisherretries 5xx/408/429 with exponential backoff + jitter, honorsRetry-After, bounded byMaxAttempts(5) +MaxElapsed(1h). Terminates on 2xx / 4xx-non-retryable.webhook.HTTPHandler— receiver middleware composing RFC 9421 verification (ProfileWebhookSigning+DigestRequired) with idempotency-based dedup scoped per authenticated sender.webhook.DecodeConfig— typed accessor that pulls the subscriber URL out ofpush_notification_config(currentlyanyon generated request types).MCPWebhookPayload,CollectionListChangedWebhook,PropertyListChangedWebhook,ArtifactWebhookPayload,RevocationNotification) withIdempotencyKey stringas a required field.IdempotencyKeyPtr()methods auto-generated fromWEBHOOK_SCHEMAS— future additions get methods for free.Safety gates
HTTPHandlerpanics whenVerificationis nil unlessAllowUnverified=trueAND a customSenderis supplied. Blocks the "unsigned receiver + attacker-controlled scope" misconfiguration the security reviewer flagged.webhook.NewSignerforce-overridesProfile = ProfileWebhookSigningso a webhook signer cannot accidentally emit a request-signing signature.Deliver/Publishershallow-clone the caller's*http.Clientand installCheckRedirect = http.ErrUseLastResponsewhen unset — signed requests MUST NOT follow redirects.Skills
skills/build-webhook-publisher/SKILL.md— canonical guide for signed webhook emission in Go agents.build-seller-agent,build-generative-seller-agent,build-retail-media-agent,build-collection-agent,build-signals-agent, andbuild-creative-agentat each skill's async/notification section.Validation
httptest.NewServer, cross-profile rejection, content-digest enforcement, dedup scope disjoint from request-side, retry classification,Retry-Afterparsing,MaxElapsed+ context cancellation, and the profile-specific wire error prefix.Verification=nilpanic gate.Delivernow actually installsCheckRedirect; returnsDeliverResultcarrying the key.Marshalmutation semantics, addedErrMissingURLdocumentation, removed import gaps from the Quickstart, clarifiedNewSigner's Profile override.Test plan
go test ./...— all 4 packages pass (adcp,idempotency,signing,webhook)go vet ./...— cleangofmt -l— clean on all new/modified filesTestSkillSkeletonsCompile— all 7 skills (including new webhook one) compile against the current SDKBreaking changes
None for existing callers. Zero-value
Profileresolves toProfileRequestSigningeverywhere it's read. Existingsigning.NewSigner,signing.Middleware, andsigning.VerifyRequestSignaturecallers continue to work without any code changes.🤖 Generated with Claude Code