Skip to content

Export the webhook key-purpose accept-set as a named constant instead of an inline literal #1448

Closed

Description

@KonstantinMirin

webhook_verifier.py builds the webhook key-purpose accept-set as an inline literal, so a
conformant receiver that wants to assert the same set has to restate it — and a restated
literal can be widened undetected.

Measured against 9.0.0b1+prs1435.1415.

Where it is

src/adcp/signing/webhook_verifier.py:179, inside the VerifyOptions the webhook route
builds for verify_request_signature:

accepted_adcp_uses=frozenset({ADCP_USE_REQUEST, ADCP_USE_WEBHOOK}),

That set is exactly what security.mdx step 8 (:1478) and the taxonomy row (:1560) mandate:
the accept-set is {request-signing, webhook-signing}, with webhook-signing deprecated per
:1438 but still required to be accepted. The value is correct — thank you for
adcp-client-python#1018, which is what made it correct. It is just not reachable by name.

grep over adcp/signing/* and adcp/webhooks* finds no exported constant for it, only this
literal and the two single-value ADCP_USE_* names it is built from.

Why that matters to a caller

A seller-side conformance shim that verifies webhooks through verify_request_signature
(rather than verify_webhook_signature) must pass accepted_adcp_uses itself, and today the
only way to match the SDK's own behaviour is to retype the literal:

accepted_adcp_uses=frozenset({ADCP_USE_REQUEST, ADCP_USE_WEBHOOK}),

A restated literal can drift from its source silently, and in the widening direction nothing
catches it. Demonstrated on our side: adding "response-signing" to that restated set left our
whole key-purpose test module green (7/7), because the tests grade the shim's behaviour against
the spec's two values and cannot see a third one the shim also accepts. "response-signing" is
precisely a value :1027 reserves for a different primitive and says MUST NOT be used for
RFC 9421 transport signatures, so that is not a hypothetical widening.

A derivation from the SDK cannot drift from the SDK. A restatement can.

Request

Export the set as a named constant so callers can consume it instead of retyping it — e.g. in
adcp/signing/constants.py alongside ADCP_USE_REQUEST / ADCP_USE_WEBHOOK:

#: The key purposes a conformant webhook verifier accepts (security.mdx step 8 / :1560).
WEBHOOK_ACCEPTED_ADCP_USES: frozenset[str] = frozenset({ADCP_USE_REQUEST, ADCP_USE_WEBHOOK})

and have webhook_verifier.py:179 use it, so the exported name and the verifier's behaviour are
the same object rather than two copies that agree today.

No behaviour change is being asked for — the set is already right. This is only about making it
nameable, so a receiver asserting the same obligation derives it rather than restating it.

Activity

  1. added
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Oct 8, 2026
  2. bokelley commented on Oct 8, 2026

    @bokelley
    Contributor

    Triage

    Classification: Enhancement — additive constant export, no behavior change
    Bucket(s): signing (label gap noted below)
    Status: ready-to-implement
    Milestone: omitted — no version target stated

    What the experts said:

    • code-reviewer: non-breaking, correct; add a unit test asserting the constant's value and document the export-scope decision in the PR body
    • ad-tech-protocol-expert: accept-set {request-signing, webhook-signing} is correct per spec; motivation is technically sound; fix the malformed :1560 docstring reference (use #1560 or a proper RST cross-reference role)

    My take: Both experts converge — the change is additive and non-breaking. The inline literal is replaced by a named constant derived from the same two ADCP_USE_* values, so the SDK's own verifier and external callers share a single definition that cannot drift independently.

    Implementation scope:

    • src/adcp/signing/constants.py — add WEBHOOK_ACCEPTED_ADCP_USES: frozenset[str] = frozenset({ADCP_USE_REQUEST, ADCP_USE_WEBHOOK}) and add it to __all__. Fix the proposed docstring: use #1018 / #1560 (plain) rather than the malformed :1560 RST cross-reference.
    • src/adcp/signing/webhook_verifier.py:179 — replace the inline literal with accepted_adcp_uses=WEBHOOK_ACCEPTED_ADCP_USES, and import the constant. No logic change.
    • adcp/signing/__init__.py — ADCP_USE_REQUEST and ADCP_USE_WEBHOOK are in constants.__all__ but are not re-exported from the package __init__. Decide explicitly: either add WEBHOOK_ACCEPTED_ADCP_USES (plus the two ADCP_USE_* names for consistency) to __init__.__all__, or leave all three out and note in the PR body that callers should import from adcp.signing.constants directly. Document the decision; do not silently omit it.
    • tests/signing/test_constants.py (or nearest equivalent) — add a unit test: assert WEBHOOK_ACCEPTED_ADCP_USES == frozenset({ADCP_USE_REQUEST, ADCP_USE_WEBHOOK}) to guard against future drift.
    • Run pytest on the signing subset, mypy src/adcp/signing/, ruff check ., black --check . before opening the PR.

    Non-breaking rationale: Only a new constant is added; no existing public symbol is removed, renamed, or changed in value. The verifier's accepted_adcp_uses argument receives the same frozenset contents as before.

    Label gap: No signing label exists in this repo. Bucket is noted in this comment; no label applied for it.


    Triaged by Claude Code. Session: https://claude.ai/code/session_01RHMZtjWdRzwCbBuTVU6uUT


    Generated by Claude Code

  3. added
    enhancementNew feature or request
    and removed
    claude-triagingTriage routine is actively working on this issue (1-3 min)
    on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions