Found while widening our own conformant-receiver test shim to match the AdCP 3.1.1 accept-set for #1291 (salesagent-z6nr.31).
Problem
signing/webhook_verifier.py hard-codes expected_adcp_use=ADCP_USE_WEBHOOK (i.e. the string "webhook-signing") when building VerifyOptions for webhook verification:
# webhook_verifier.py:152
options = VerifyOptions(
...,
expected_adcp_use=ADCP_USE_WEBHOOK,
)
verifier.py's _check_key_purpose (:607-621) then rejects any JWK whose adcp_use isn't byte-equal to that one pinned value, raising REQUEST_SIGNATURE_KEY_PURPOSE_INVALID:
# verifier.py:621
if jwk.get("adcp_use") != expected_adcp_use:
raise SignatureVerificationError(REQUEST_SIGNATURE_KEY_PURPOSE_INVALID, step=8, ...)
Per AdCP 3.1.1 security.mdx, this is backwards. Step 8 of the verifier checklist reads:
Verify the JWK's use is "sig", key_ops includes "verify", and adcp_use is "request-signing" ... the deprecated "webhook-signing" value MUST also be accepted for backward compatibility. Reject on any other outcome with webhook_signature_key_purpose_invalid.
And :1438 states plainly: "webhook-signing" is DEPRECATED; verifiers MUST still accept it, but new signers SHOULD publish and sign with "request-signing" keys only.
So the accept-set for a webhook verifier is {"request-signing", "webhook-signing"} — never a single value in either direction. As currently written, webhook_verifier.py accepts only the deprecated value and rejects the one the spec directs new signers to use, which is the exact inverse of the intended backward-compatibility behavior.
Suggested direction
Widen the webhook verifier's key-purpose check to accept either ADCP_USE_REQUEST or ADCP_USE_WEBHOOK, matching the request-signing verifier's own (correct) handling, rather than pinning VerifyOptions.expected_adcp_use to a single value. Since expected_adcp_use on VerifyOptions is a single string compared with !=, this likely needs either a small accept-set parameter on VerifyOptions/_check_key_purpose, or the webhook verifier trying both purposes itself.
Workaround in our own tests
We widen our own test-side conformant-receiver shim to attempt "request-signing" first and retry once with "webhook-signing" only on REQUEST_SIGNATURE_KEY_PURPOSE_INVALID, so our own signing keys are graded correctly while this is open.
Found while widening our own conformant-receiver test shim to match the AdCP 3.1.1 accept-set for #1291 (salesagent-z6nr.31).
Problem
signing/webhook_verifier.pyhard-codesexpected_adcp_use=ADCP_USE_WEBHOOK(i.e. the string"webhook-signing") when buildingVerifyOptionsfor webhook verification:verifier.py's_check_key_purpose(:607-621) then rejects any JWK whoseadcp_useisn't byte-equal to that one pinned value, raisingREQUEST_SIGNATURE_KEY_PURPOSE_INVALID:Per AdCP 3.1.1
security.mdx, this is backwards. Step 8 of the verifier checklist reads:And
:1438states plainly:"webhook-signing"is DEPRECATED; verifiers MUST still accept it, but new signers SHOULD publish and sign with"request-signing"keys only.So the accept-set for a webhook verifier is
{"request-signing", "webhook-signing"}— never a single value in either direction. As currently written,webhook_verifier.pyaccepts only the deprecated value and rejects the one the spec directs new signers to use, which is the exact inverse of the intended backward-compatibility behavior.Suggested direction
Widen the webhook verifier's key-purpose check to accept either
ADCP_USE_REQUESTorADCP_USE_WEBHOOK, matching the request-signing verifier's own (correct) handling, rather than pinningVerifyOptions.expected_adcp_useto a single value. Sinceexpected_adcp_useonVerifyOptionsis a single string compared with!=, this likely needs either a small accept-set parameter onVerifyOptions/_check_key_purpose, or the webhook verifier trying both purposes itself.Workaround in our own tests
We widen our own test-side conformant-receiver shim to attempt
"request-signing"first and retry once with"webhook-signing"only onREQUEST_SIGNATURE_KEY_PURPOSE_INVALID, so our own signing keys are graded correctly while this is open.