Context
Round-4 webhooks DX exploration (PR #205) found that every seller implementing webhook emission rewrites ~30 lines of boilerplate:
- Read
push_notification_config / reporting_webhook from the request
- Build the payload via
adcp.webhooks.create_mcp_webhook_payload(...) or create_a2a_webhook_payload(...) (branch on transport)
- Sign with
adcp.webhooks.get_adcp_signed_headers_for_webhook(...)
- POST via httpx with
json=payload, headers=signed
- Echo
PushNotificationConfig.token into the payload for buyer-side auth
- Handle
authentication.schemes[] (Bearer, HMAC, HMAC+timestamp)
- Handle retries with byte-identical re-POST
The signer's serialization-format mismatch with json= was already silent-failing across SDKs before PR #205 fixed it. That bug lived in that boilerplate — wherever the seam is between "build payload" and "POST payload," there's room for drift. Collapsing the seam kills the class of bug.
Proposal
async def deliver(
config: PushNotificationConfig | ReportingWebhook,
payload: dict | AdCPBaseModel,
*,
client: httpx.AsyncClient | None = None,
secret: str | None = None, # falls through to config.authentication
) -> httpx.Response:
\"\"\"One-shot webhook dispatch. Handles signing, token echo, transport
branching, auth scheme selection, and compact-separator JSON serialization.
Returns the response for status inspection.\"\"\"
The helper collapses steps 3-6 (and owns step 2 as a convenience) into one call, routes the right payload builder based on the transport hint in config, and signs against the same bytes it POSTs — making the signer/wire-format drift structurally impossible.
Acceptance
- New
adcp.webhooks.deliver() function + tests covering: happy path (MCP + A2A), HMAC auth, Bearer auth, token echo from PushNotificationConfig.token, retry idempotency (re-POST produces identical bytes).
- Seller skill's "Emitting Webhooks" section switches to
await deliver(config, payload) as the primary pattern. The manual 6-step path stays documented for advanced users who need to customize.
Priority
4.1 — DX improvement, not a bug fix. The fix for the signer/wire-format drift landed in 4.0 (PR #205); the helper would have made it harder for that bug to exist in the first place.
Surfaced during round-4 DX exploration: #205
Context
Round-4 webhooks DX exploration (PR #205) found that every seller implementing webhook emission rewrites ~30 lines of boilerplate:
push_notification_config/reporting_webhookfrom the requestadcp.webhooks.create_mcp_webhook_payload(...)orcreate_a2a_webhook_payload(...)(branch on transport)adcp.webhooks.get_adcp_signed_headers_for_webhook(...)json=payload, headers=signedPushNotificationConfig.tokeninto the payload for buyer-side authauthentication.schemes[](Bearer, HMAC, HMAC+timestamp)The signer's serialization-format mismatch with
json=was already silent-failing across SDKs before PR #205 fixed it. That bug lived in that boilerplate — wherever the seam is between "build payload" and "POST payload," there's room for drift. Collapsing the seam kills the class of bug.Proposal
The helper collapses steps 3-6 (and owns step 2 as a convenience) into one call, routes the right payload builder based on the transport hint in
config, and signs against the same bytes it POSTs — making the signer/wire-format drift structurally impossible.Acceptance
adcp.webhooks.deliver()function + tests covering: happy path (MCP + A2A), HMAC auth, Bearer auth, token echo fromPushNotificationConfig.token, retry idempotency (re-POST produces identical bytes).await deliver(config, payload)as the primary pattern. The manual 6-step path stays documented for advanced users who need to customize.Priority
4.1 — DX improvement, not a bug fix. The fix for the signer/wire-format drift landed in 4.0 (PR #205); the helper would have made it harder for that bug to exist in the first place.
Surfaced during round-4 DX exploration: #205