Skip to content

fix(webhooks)!: comply with AdCP 3.2 challenge schema - #1897

Merged
rachitm022 merged 1 commit into
mainfrom
rachitm022/adcp-3.2-webhook-payload-fix
Nov 13, 2026
Merged

rachitm022 merged 1 commit into
mainfrom
rachitm022/adcp-3.2-webhook-payload-fix

Conversation

@rachitm022

@rachitm022 rachitm022 commented Nov 13, 2026 •

Copy link
Copy Markdown
Collaborator

Why

create_webhook_challenge_payload() omitted fields required by the bundled AdCP 3.2 schema, preventing durable webhook registration consumers from validating the challenge. This also unblocks a subsequent SDK prerelease for embedded-sales-agent.

Linear: AI-21266

What

  • require and emit seller_agent_url, delivery_auth, and event_types
  • validate generated payloads against the bundled webhook challenge schema
  • bind deprecated Bearer/HMAC delivery configuration by credential fingerprint while always signing the challenge with RFC 9421
  • accept current request-signing JWKs and compatibility webhook-signing JWKs
  • reject non-RFC 9421 challenge senders before network I/O
  • update examples and add schema, validation, signing, and compatibility coverage
  • leave receiver response validation unchanged

Migration

Callers of create_webhook_challenge_payload(), challenge_webhook_destination(), and WebhookSender.send_webhook_challenge() must pass the seller's agent URL and the pending subscription's event types. challenge_webhook_destination() now always requires an RFC 9421 WebhookSender. For durable Bearer/HMAC configurations, continue passing authentication alongside that sender so the signed challenge includes the delivery mode and credential fingerprint without exposing the credential.

Testing

  • uv run pytest -q tests/test_webhook_challenge.py tests/conformance/signing/test_webhook_sender_e2e.py tests/test_webhook_signing_capabilities.py — 89 passed
  • uv run black --check ... — passed
  • make validate-generated — passed
  • make ci-local — lint, mypy, strict adopter checks, pyright, and coverage passed; 18,885 tests passed and 2,828 skipped. Ten unrelated reporting tests failed locally: three timing failures passed on immediate rerun, while the remaining gates require Linux /proc, /etc/os-release, or Linux process-group behavior and cannot pass in this macOS workspace. Linux CI is the authoritative run for those gates.

BREAKING CHANGE: Webhook challenge helpers now require seller_agent_url and event_types, and proof challenges require an RFC 9421 WebhookSender; pass legacy delivery authentication alongside that sender.

Comment thread src/adcp/webhooks.py Resolved
Comment thread src/adcp/webhooks.py Resolved

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Request changes

Request changes — 2 blocking finding(s)

Blocking findings

  • src/adcp/webhooks.py:1103 — Breaking public-API signature change under non-breaking fix: prefix. create_webhook_challenge_payload, challenge_webhook_destination (both in adcp.__all__) and WebhookSender.send_webhook_challenge gain required params seller_agent_url/event_types, raising TypeError for existing callers. Per the repo's mandatory semver-signal rule, this needs fix!:/feat!: or a BREAKING CHANGE: footer plus a migration note; release-please would otherwise cut a patch and ship the break without a major.
  • src/adcp/webhooks.py:1444 — challenge_webhook_destination now rejects legacy-only (no RFC 9421 sender) callers, another breaking public-surface change shipped under a non-breaking prefix.

Both blockers share one root cause: a breaking public-API change on the adcp.* namespace without the required semver signal. Fix by relabeling the commit (fix!:/feat!: + migration note), not necessarily by reverting the behavior.

Positives verified by the reviewer: credential never ships (asserted at tests/test_webhook_challenge.py:616), the pre-network signs_with_rfc9421 guard fails closed, and legacy-auth ValueErrors map to invalid_configuration. One non-blocking posture note: WebhookSender.from_jwk now accepts request-signing keys for webhook signing — worth confirming the signing-profile change is sanctioned under AdCP 3.2 upstream.

(Row 1 of the decision table: high findings present → request-changes.)

Blocking findings

  • src/adcp/webhooks.py:1103 — Breaking public-API signature change under non-breaking fix: prefix (required params added to public exports; needs fix!/BREAKING CHANGE + migration note)
  • src/adcp/webhooks.py:1444 — challenge_webhook_destination now rejects legacy-only callers, a breaking change under a non-breaking prefix

@rachitm022 rachitm022 changed the title fix(webhooks): comply with AdCP 3.2 challenge schema fix(webhooks)!: comply with AdCP 3.2 challenge schema Nov 13, 2026
BREAKING CHANGE: Webhook challenge helpers now require seller_agent_url and event_types, and proof challenges require an RFC 9421 WebhookSender; pass legacy delivery authentication alongside that sender.
@rachitm022
rachitm022 force-pushed the rachitm022/adcp-3.2-webhook-payload-fix branch from 78c0592 to bedea16 Compare November 13, 2026 02:53
@aao-secretariat
aao-secretariat Bot dismissed their stale review November 13, 2026 02:56

Superseded by a later Ladon review that rechecked and resolved its blocking findings.

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — prior blockers resolved, clean delta.

Both prior high findings are now resolved: the breaking public-API changes to create_webhook_challenge_payload, challenge_webhook_destination, and WebhookSender.send_webhook_challenge (all in adcp.__all__) now ship under fix(webhooks)!: with a BREAKING CHANGE: footer and a Migration section naming all three functions, satisfying the mandatory semver signal (release-please cuts a major).

New delta reviewed clean: the always-signed sender path preserves the body-size cap, reserved-header validation, bounded timeout, and IP-pinned transport; the credential is never shipped (fingerprint-only, asserted at tests/test_webhook_challenge.py:616); send_webhook_challenge fails closed on non-RFC-9421 senders before network I/O; payloads validate against the bundled 3.2 webhook-challenge schema; and the loosened from_jwk only accepts WEBHOOK_ACCEPTED_ADCP_USES already honored by the verifier in base.

No blocking or medium findings. high_risk is true only for two (modified) signing files, but with no medium-or-higher findings on them the modifications are presumed safe — no escalation trigger. No author-team gate; gated_paths is false. Falls through to row 9.

@rachitm022 rachitm022 added the forward-port next Needs a port to the next draft-spec branch before the regular main merge (see RELEASING.md) label Nov 13, 2026

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve.

Clean pass with no critical/high/medium findings. Walking the decision table top-down:

  • Rows 1 (critical/high) — no findings.
  • Row 2 (gated_paths) — gated_paths is false; also review_decision is APPROVED. Not triggered.
  • Rows 3–5 (high_risk escalations) — high_risk is true but all reasons are (modified) with no medium-or-higher findings, so no deletion and no medium concern on a modified sensitive file. Not triggered.
  • Row 6 (sticky escalate) — prior decision was approve, not escalate. Not triggered.
  • Row 7 (no-auto-approve team) — no team match.
  • Row 8 (3+ mediums) — zero medium findings.
  • Row 9 — approve.

Substantive checks confirmed by the reviewer: the breaking change to create_webhook_challenge_payload, challenge_webhook_destination, and WebhookSender.send_webhook_challenge (all public adcp.* exports) correctly carries the fix(webhooks)!: / BREAKING CHANGE semver signal plus a migration note, satisfying the mandatory public-surface semver gate. New payload shape validates against the bundled 3.2.7 core/webhook-challenge.json with matching required set and additionalProperties: false. No credential leakage — legacy Bearer/HMAC auth bound by SHA-256 fingerprint only, verified by an explicit no-leak assertion. Fail-closed RFC 9421 sender requirement matches the schema MUST. Timeouts preserved on the send path. jsonschema is a core dependency so the hard validation requirement is safe.

@rachitm022
rachitm022 enabled auto-merge November 13, 2026 03:38
@rachitm022
rachitm022 disabled auto-merge November 13, 2026 03:52
@rachitm022
rachitm022 enabled auto-merge November 13, 2026 03:52
@rachitm022 rachitm022 added the bug Something isn't working label Nov 13, 2026

@aao-secretariat aao-secretariat Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ladon verdict: Approve

Approve — clean subsequent pass at the same HEAD as the prior approve.

No critical, high, or medium findings. The reviewer re-reviewed all 10 files and confirmed:

  • create_webhook_challenge_payload (webhooks.py:1114-1152) emits AdCP 3.2 required fields and validates against the bundled core/webhook-challenge.json before returning; wire shape matches schema (seller_agent_url/delivery_auth/event_types required, delivery_auth allOf gating credential_fingerprint).
  • Legacy Bearer/HMAC credentials are sha256-fingerprinted, never emitted (test asserts credentials absent from serialized payload); challenges fail-closed on non-RFC9421 senders — satisfies the no-credentials-in-payload posture.
  • Signing-key posture change matches pre-existing WEBHOOK_ACCEPTED_ADCP_USES / adcp#5555 and the verifier — not spec drift.
  • Breaking signature changes carry fix!: + BREAKING CHANGE footer + migration note — semver signal correct.

Decision table: no critical/high (row 1 n/a); gated_paths false and review_decision APPROVED (row 2 n/a); high_risk reasons are only (modified) with no medium finding (rows 3-5 n/a); prior decision was approve so row 6 n/a; no no-auto-approve team match (row 7 n/a); zero medium findings (row 8 n/a). Falls through to row 9 — approve. The high_risk flag covers two modified signing files, but with no medium-or-higher findings the modifications are presumed safe.

@rachitm022
rachitm022 disabled auto-merge November 13, 2026 13:38
@rachitm022
rachitm022 merged commit 11ed387 into main Nov 13, 2026
49 checks passed
@rachitm022
rachitm022 deleted the rachitm022/adcp-3.2-webhook-payload-fix branch November 13, 2026 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working forward-port next Needs a port to the next draft-spec branch before the regular main merge (see RELEASING.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants