> ## Documentation Index
> Fetch the complete documentation index at: https://docs.adcontextprotocol.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Web Bot Auth request-signing profile

> Experimental AdCP 3.3 Web Bot Auth profile for request and webhook signing: keys from Signature-Agent, an origin as the identity, and one web-bot-auth signature.

This page defines an AdCP profile of Web Bot Auth (WBA), the IETF protocol for identifying automated HTTP clients. Under the profile, a seller identifies a signing agent from the request itself. The signer names its origin in a covered `Signature-Agent` header, and the seller fetches the signer's keys from that origin's key directory. The seller then decides, under AdCP's own rules, what the identified agent may do.

> [!NOTE]
>
> **Status: experimental in AdCP 3.3**, adopted by [DR-0023](https://github.com/adcontextprotocol/adcp/blob/main/governance/decisions/DR-0023-agent-identity-is-an-origin.md). The profile is published alongside the AdCP RFC 9421 profile in [Signed Requests (Transport Layer)](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#signed-requests-transport-layer), which stays as written. Document signing and transparency logs are separate decisions and are not part of this page.

The profile selects options that WBA and RFC 9421 already define. It adds no headers, tags, key fields, or error codes to the wire. Any implementation of [draft-ietf-webbotauth-httpsig-protocol-00](https://datatracker.ietf.org/doc/draft-ietf-webbotauth-httpsig-protocol/00/) can therefore sign and verify AdCP requests once it is configured with the rules on this page. Section numbers in this page refer to that draft unless they name RFC 9421.

## What the profile changes for a seller

Under the AdCP RFC 9421 profile, a seller verifies a signature only from an agent it has onboarded. The agent's key location comes from onboarding or from a registry the seller trusts. Under this profile, the seller learns the signer's identity from the first signed request. Onboarding becomes approving a verified identity instead of issuing a credential. The seller approves an identity from a domain allowlist, from a commercial agreement, or with one click in its admin interface. After that, every signed request from the identity is authenticated without a bearer token. Until the seller approves the identity, the identity gets no account access, reads included. See [Onboarding](#onboarding).

## One resolution rule

A verifier resolves a signer's keys in one step:

```text theme={null}
Signature-Agent member  ->  origin  ->  https://<origin>/.well-known/http-message-signatures-directory
```

The verifier reads the `Signature-Agent` member keyed to the signature label and takes the origin it names. The member value is exactly the ASCII serialization of an `https` origin (RFC 6454 section 6.2), such as `https://buyer-7k3q.com`: a lowercase host, no default port, no trailing `/`, and no path, query, fragment, or userinfo (sections 5.2.1 and 5.5). A signer sends that canonical origin. A verifier compares the value with it as a string, without normalizing, and refuses any other value, including an `http` origin, with `403`. It fetches the HTTP Message Signatures Directory at that origin's well-known path (section 5.5). One fetch returns one file, and the verifier reads no other document to find keys. The directory is the only key location the profile recognizes. The verifier ignores a `Signature-Agent` member with a `type` parameter other than `directory`, as section 5.2.1 permits for a type the verifier does not support. Section 5.2.1 defines `type` as a Token, and a verifier accepts `type=directory` as a Token or as a String, as the reference library does.

Section 5.5 explains why the well-known path matters: "TLS authenticates the host but not the path", and "The well-known URI is reserved, so directory additionally names a domain".

## Identity

Under the profile, an identity is an origin, for example `https://buyer-7k3q.com`. The identity has one key directory, and every key it signs with is published there. The verifier attributes a verified request to that origin and to nothing else.

Two agents cannot share an origin. An operator that runs several agents gives each its own origin, for example one subdomain per agent. Section 6.5 recommends this for agents that need to be told apart: "these should use distinct signing keys and signing key directories". The same rule covers purposes: a governance agent is an agent like any other, on its own origin (see [One agent per purpose](#one-agent-per-purpose)).

`brand.json`, `adagents.json`, and a seller's onboarding records refer to an identity by its origin. So does [`trust.json`](/dist/docs/3.3.0-beta.0/reference/trust-json): it lists agent origins with their roles and carries no keys. A multi-tenant vendor gives each tenant its own subdomain, for example `https://7f3a9c.sentinel-governance.example`, which serves that tenant's key directory.

An agent that shares a host with other agents under the AdCP RFC 9421 profile keeps using that profile until it moves to its own origin. When it moves, the seller maps the agent's principal to that origin explicitly. Key discovery therefore never changes who the seller considers the caller to be.

Key lookup is keyed on the pair of origin and key, never on the key alone (section 5.4). A verifier that indexes keys by thumbprint alone attributes a request to whatever origin the signer claims. Negative vector `004-key-not-in-named-directory` grades this rule.

## One agent per purpose

Purpose belongs to the agent, not to the key. A governance agent is an identity like any other, on its own origin, with its own key directory, so no key and no directory states a purpose. JWS artifacts, such as governance tokens, are outside this profile.

## Profile rules

RFC 9421 section 3.2.1 lets an application impose requirements beyond the base specification. Its examples include "Requiring a specific set of header fields to be signed", "Enforcing a maximum signature age", and "Enforcing uniqueness of the nonce parameter". The profile consists of the following rules. Each rule is a requirement of that kind or a choice among options WBA defines.

| Rule | Profile choice | Basis |
| - | - | - |
| Signature | RFC 9421 with `tag="web-bot-auth"`, `keyid` set to the key's JWK thumbprint, and a covered `Signature-Agent` member whose value is the ASCII serialization of an `https` origin (RFC 6454 section 6.2), with no path, query, fragment, or userinfo | Sections 5.2, 5.2.1, 5.5 |
| `Signature-Agent` value | Exactly the ASCII serialization of an `https` origin: a lowercase host, no default port, no trailing `/`, and no path, query, fragment, or userinfo. A verifier compares the string without normalizing and refuses any other value with `403` | Sections 5.2.1 and 5.5; RFC 6454 section 6.2 |
| Components, every request | `@method`, `@target-uri`, `@authority`, `"signature-agent";key=<label>` | Section 5.2 |
| Components, requests with a body | Also `content-type` and `content-digest` | Section 5.2: an agent that needs body integrity "MUST send and cover Content-Digest" |
| Parameters | `created`, `expires`, `keyid`, `alg`, and a `nonce` on every signed request. The nonce is 64 random bytes, encoded as unpadded base64url or as canonical padded base64 | RFC 9421 sections 2.3, 3.2.1, 7.2.2; sections 5.2 and 5.2.3; Appendix C.6; WBA architecture draft section 4.2.2 and Appendix A |
| Algorithm | `ed25519`, named in `alg`, which must match the key | RFC 9421 sections 2.3, 3.2.1, 7.3.6 |
| Lifetime and clock skew | `expires` at most 300 seconds after `created`. The verifier allows 60 seconds of clock skew | RFC 9421 section 3.2.1 |
| Key publication | Each identity publishes its keys only in its key directory. Each signing key is an Ed25519 key (`kty` `OKP`, `crv` `Ed25519`). `kid` is the thumbprint, so a verifier selects a key by matching `keyid` against `kid` | Sections 5.5, 5.5.1, 6.5; RFC 8037 |
| Directory responses | Served with the media type `application/http-message-signatures-directory+json`. Signed, one signature per Ed25519 key, covering `"@authority";req` and `content-digest`, with `created`, `expires`, the key's thumbprint as `keyid`, and `tag="http-message-signatures-directory"`. A verifier uses each Ed25519 key that has a valid signature on the response, ignores every other key and its signatures, and refuses a directory in which any key carries a private member | Section 5.5.1; Appendix B.1; directory draft section 5; RFC 7517 section 5 |
| Fetches | Directory and revocation-list fetches follow AdCP's fetch rules: HTTPS only, resolved-address checks, no redirects, and size and time limits | Sections 5.5 and 6.7; [Webhook URL validation (SSRF)](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#webhook-url-validation-ssrf) |
| One signature | A request carries exactly one `web-bot-auth` signature and exactly one `Signature-Agent` member, keyed to that signature's label. A verifier does not read `Signature-Input` members with other tags. An intermediary that signs a request is its signer; whether it may act for another party is an authorization question answered by that party's `brand.json` or `trust.json`, not by a second signature | RFC 9421 section 3.2.1 lets an application add a requirement of this kind. Section 5.2.2 permits more than one signature ("A request MAY contain more than one Web Bot Auth signature") and does not require a verifier to accept one |
| Failure responses | `400` when a signature header does not parse, `403` when verification or a profile rule fails, and `429` for a reused signature or nonce | Sections 5.3 and 5.4 |

A request that follows the profile is a valid WBA request. A WBA verifier configured with these rules accepts it and rejects a request that falls short. Because the rules are fixed in the profile, a signer knows them before its first request.

AdCP adds two checks that run outside the WBA verifier, after it returns: the revocation-list check on a verified request, and the contents of `Accept-Signature` on a refusal. [AdCP checks after Web Bot Auth verification](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#adcp-checks-after-web-bot-auth-verification) defines both. A standard WBA library does not perform them, and the WBA conformance vectors do not grade them.

The profile uses one algorithm. Every WBA library signs and verifies Ed25519, and several WBA tools support no other algorithm.

The two nonce encodings are the two the WBA drafts use. The architecture draft's text names base64url, and the examples in both drafts use padded standard base64. Cloudflare's `web-bot-auth` library accepts exactly these two forms in `validateNonce`, and its `generateNonce` produces padded standard base64. A verifier accepts either encoding, so the profile states no preference for signers.

The 300-second ceiling on `expires` is provisional. WBA section 5.2 recommends no more than 24 hours for general automated traffic, and the RFC asks the working group to confirm the shorter value for spend-committing traffic.

### A signed request

A buyer agent at `buyer-7k3q.com` sends `create_media_buy` to a seller over MCP. The domains are placeholders that resolve to nothing, and long lines are wrapped per RFC 8792.

```http theme={null}
NOTE: '\' line wrapping per RFC 8792

POST /mcp/ HTTP/1.1
Host: seller-7k3q.com
Content-Type: application/json
Content-Digest: sha-256=:yeYok+hSZEsVifM6QdAl+jXRfSJ/75y4NS4MXUWaOdU=:
Signature-Agent: sig1="https://buyer-7k3q.com"
Signature-Input: sig1=("@method" "@target-uri" "@authority" "content-type" \
    "content-digest" "signature-agent";key="sig1");created=1790841600;expir\
    es=1790841900;nonce="Mn-ZM8SnGESx0-rmyYMuTkJFerfbwEii2C0qNkDP8ugpcuv0pc\
    rt6Da2ifr-6MTmfVeZhnFymLeAofeXJbFaxA";keyid="6WsKMCObba49V3uN0vIENnk1BK\
    Ak7c9zjBJcWKyQ9_I";alg="ed25519";tag="web-bot-auth"
Signature: sig1=:ihPGBC7XQPM6WkIYhYQvFHgwTpbssvuQzkb0FfwkuebREyF32sXhiRxXbX\
    WRZMp0O//C4I2g1a2Kui56ffEfBQ==:

{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"create_medi\
    a_buy","arguments":{"idempotency_key":"4f6c1a0e-8d2b-4b7e-9c3a-2e5d7f1b\
    0a96","account":{"account_id":"acc_42"},"proposal_id":"prop_7","total_b\
    udget":{"amount":25000,"currency":"USD"}}}}
```

The signature base the buyer signed is the one in `positive/001-single-signer-request.json` under [Conformance vectors](#conformance-vectors). The `Signature-Agent` value is an origin, so the verifier fetches `https://buyer-7k3q.com/.well-known/http-message-signatures-directory`.

## Key directories

A key directory is a JWK Set served at `/.well-known/http-message-signatures-directory` with the media type `application/http-message-signatures-directory+json` (sections 5.5.1 and 8.2). Each key's `kid` is its JWK thumbprint, computed per RFC 7638 and, for Ed25519 keys, RFC 8037 Appendix A.3. Publishers omit `alg` on Ed25519 keys. A publisher adds a key to the directory before signing with it, as section 5.5.2 describes, so that verifiers can resolve it on first use.

The directory server signs its response once per Ed25519 key (Appendix B.1). Each signature covers `"@authority";req` and `content-digest`, carries `created` and `expires`, uses the key's thumbprint as `keyid`, and uses `tag="http-message-signatures-directory"`. Covering the request authority stops the key set from being served again from another domain. A verifier validates each signature with the key the directory itself provides and validates `Content-Digest` against the body; it rejects a response signature whose `created` is in the future. The verifier stores the signed response as its record of the fetch.

A verifier uses each Ed25519 key that has a valid signature on the directory response, and ignores every other key and its signatures. Appendix B.1 lets a verifier use a key without a valid proof, and says the proof "matters for a domain-bound identifier". An AdCP identity is an origin, so the profile uses only proven keys. The directory draft says the same in section 5: "Clients SHOULD ignore keys from a directory response that do not have a corresponding valid signature". The keys a verifier ignores include keys the profile does not use, such as a P-256 key for another WBA use (section 5.5.1 permits any registered algorithm), and keys it cannot import, including a malformed Ed25519 key, as RFC 7517 section 5 advises. A directory with no validly signed Ed25519 key yields no usable key, so a request signed with one of its keys fails as an unknown key.

A verifier refuses a directory in which any key carries a private member (`d`, `p`, `q`, `dp`, `dq`, `qi`, `oth`, or `k`), whatever the key's type. It compares member names without regard to case, so `D` is refused as well: some JSON decoders match member names case-insensitively and would read `D` as the private scalar. It checks every key for these members before it skips the keys the profile does not use, so a P-256 key with a `d` member is refused, not ignored. Section 5.5.1 says that "A verifier SHOULD validate the directory format and reject malformed entries", and a directory that publishes private key material is malformed.

```http theme={null}
NOTE: '\' line wrapping per RFC 8792

HTTP/1.1 200 OK
Content-Type: application/http-message-signatures-directory+json
Cache-Control: max-age=300
Content-Digest: sha-256=:RQDAkRyNAdoJ2+MThuVDCXhj13Or2jS6ewwSRuyaXQg=:
Signature-Input: key1=("@authority";req "content-digest");created=179084100\
    0;expires=1790927400;keyid="6WsKMCObba49V3uN0vIENnk1BKAk7c9zjBJcWKyQ9_I\
    ";alg="ed25519";tag="http-message-signatures-directory"
Signature: key1=:QJeeYQ9E5OXMbEy89f1YjQNCB5NjRxN+ck/V+4sRoVNtF5S/446uVIECNC\
    eANo6YH/DhOBG/jMzNZLDqQrmkBg==:

{
  "keys": [
    {
      "kty": "OKP",
      "crv": "Ed25519",
      "kid": "6WsKMCObba49V3uN0vIENnk1BKAk7c9zjBJcWKyQ9_I",
      "x": "yIxM3SIY89_Yhr6EA9hik-AJjWSDPraK5chDqQ3Xlf0",
      "use": "sig"
    }
  ]
}
```

**Fetch rules.** The verifier fetches a directory over HTTPS only, checks the resolved address before connecting, and follows no redirects (section 5.5). It applies the size, key-count, and time limits of section 6.7 and AdCP's fetch budgets: a 256 KiB body cap and 5 s connect and 10 s total deadlines. The verifier treats any status other than `200` as a discovery failure. It rejects a directory response whose media type is not `application/http-message-signatures-directory+json`, which section 5.5.1 requires of a directory served at the well-known path.

**Caching.** A directory that resolves and does not contain the key is evidence that the key was removed, and it replaces the cached entry. A directory that fails to resolve is not evidence and does not evict a cached entry (section 6.10). Negative cache entries expire after at most 60 s, within the five-minute bound of Appendix C.5. A cached directory verifies until the earliest `expires` among its signatures. During a discovery failure, the fail-closed rule of the revocation list bounds spend-committing requests (see [AdCP checks after Web Bot Auth verification](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#adcp-checks-after-web-bot-auth-verification)). A compromised key is revoked, not removed.

## Verification

A verifier applies the following procedure to a request that carries `Signature-Input`.

1. Parse `Signature`, `Signature-Input`, and `Signature-Agent` as Structured Fields. Every `Signature` member value is a Byte Sequence and every `Signature-Input` member value is an Inner List (RFC 9421 sections 4.1 and 4.2), whatever the member's tag. If any header fails to parse, answer `400` (section 5.4).
2. Read the one `Signature-Input` member whose `tag` is `web-bot-auth` and the `Signature-Agent` member with the same label. The verifier does not read members with other tags. A request that carries more than one `web-bot-auth` member or more than one `Signature-Agent` member is rejected with `403`. If the `Signature-Agent` member is absent, or the signature does not cover `"signature-agent";key=<label>`, reject the signature. An uncovered member is a claim the verifier must not attach policy to (section 4.1).
3. Resolve the origin the member names to its key directory, from cache or by fetching it, and select the key whose `kid` equals `keyid`. If the directory holds no such key, answer `403`.
4. Verify the signature with the selected key, and check its covered components, parameters, and window against the rules in [Profile rules](#profile-rules). If the signature fails verification or the profile rules, answer `403`, as described in [Error responses](#error-responses).
5. Check the `nonce` against the identity's replay state (see [Replay protection](#replay-protection)). If the identity used it within the signature's lifetime, answer `429`.
6. Record the `nonce` in the identity's replay state. The verifier records a nonce only after steps 1 to 5 pass, so a request that fails verification can't use up a nonce that its legitimate signer hasn't sent yet.
7. Run the AdCP checks in [AdCP checks after Web Bot Auth verification](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#adcp-checks-after-web-bot-auth-verification), starting with the identity's revocation list.
8. Pass the verified identity to the AdCP layer, which applies [Onboarding](#onboarding) and the account authorization rules that apply to every request, signed or not.

Steps 1 to 6 are WBA verification with the profile's rules, and a WBA library configured with those rules performs them. Steps 7 and 8 belong to the AdCP layer.

When a deployment verifies at the edge, Appendix C.10 applies: the edge "can instead verify at the proxy and pass the result to the origin through a deployment-local trusted channel". The edge removes any copy of that result a client sends, and the origin accepts the result only over its authenticated channel to its own edge. The edge forwards the signature fields unchanged, because the seller's own verifier enforces nonce uniqueness whatever the edge does.

## Onboarding

A verified identity gets account access only after the seller binds it to a principal, under rule R5 of [Agent identity and buyer self-onboarding](https://github.com/adcontextprotocol/adcp/blob/main/specs/agent-identity-3-3.md). Until then it gets what an anonymous caller gets, reads included. After binding, the principal resolves to its accounts, and every later signed request from the identity is authenticated without a bearer token.

A seller approves an identity from a domain allowlist, from a commercial agreement, or with one click in its admin interface. For one-click approval, the seller records the identity for review and rejects the request with an AdCP authorization error. It never runs a queued request later, because the request's signature expires within minutes; after approval, the buyer sends the request again. A seller limits how many identities one origin or one network address can submit for review.

The account's operator comes from the request's `account.operator`, not from the authenticated identity. Whether the identity may act for the brand named in the request is the authorization check that [Agent identity](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#agent-identity) separates from authentication. This profile changes how the seller learns who is calling, not what the seller lets the caller do.

## Replay protection

The seller's verifier MUST reject a `nonce` it has already seen for the same identity within the signature's lifetime, whether or not an edge verifier ran first.

The profile requires the nonce on every signed request, reads included. A replayed read returns the response to whoever sent the replay, and proxies and log pipelines see request headers. RFC 9421 section 7.2.2 describes the nonce as "a per-message unique value to allow the verifier to detect replay of the signature itself". WBA leaves nonce enforcement to deployment policy (Appendix C.6); the profile makes that choice once, so each seller does not have to.

`idempotency_key` keeps its role. A repeated committed mutation returns the stored response without re-running side effects, under [Idempotency](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#idempotency). The signature covers `content-digest`, so it binds the key too.

Replay-cache sizing is implementation guidance. The entry lifetime for a nonce is the remaining signature validity plus the 60-second skew allowance. The recommendations in [Transport replay dedup](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#transport-replay-dedup) carry over with the identity in place of `keyid`.

## Error responses

The profile reports signature failures with HTTP status codes, as WBA does (sections 5.3 and 5.4):

| Status | Meaning for the caller | Basis |
| - | - | - |
| `400` | The signature headers cannot be parsed. Fix the signer. | Section 5.4 |
| `403` | The signature failed verification or a profile rule. Sign again under the profile rules. | Section 5.3 |
| `429` | The signature or its nonce was used before. Sign again with a fresh `nonce`. | Section 5.3; RFC 9421 section 7.2.2 |

On a `403` or `429`, the AdCP layer SHOULD add an `Accept-Signature` field that names the components and parameters the profile requires. [AdCP checks after Web Bot Auth verification](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#adcp-checks-after-web-bot-auth-verification) states its contents. The conformance vectors grade the status, not that field.

A request signed with a revoked key gets the error that [Transport revocation](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#transport-revocation) states. An identity without the needed onboarding entry gets an AdCP authorization error, without `Accept-Signature`. Those errors use the AdCP error envelope and codes, not the transport status codes in the table.

An unsigned request to an operation in `required_for`, from a caller with no other credential the verifier accepts, gets an answer that depends on what the verifier advertises. A verifier that advertises only `web-bot-auth` answers `403`. A verifier that also advertises the AdCP RFC 9421 profile answers as that profile does: `401` with `request_signature_required`. In both cases the AdCP layer SHOULD add `Accept-Signature` for this profile.

## Webhooks

A webhook signed under this profile is an ordinary signed request from the seller's identity. The seller signs the delivery with the rules in [Profile rules](#profile-rules). Its `Signature-Agent` member names the seller's origin, and the signature covers `content-type` and `content-digest`. The receiver resolves the seller's keys from the seller's key directory with the same one resolution rule, and applies [Verification](#verification) and [Replay protection](#replay-protection) as a seller does.

A subscriber states which profile it verifies in the optional `signing_profile` field of every webhook registration: `push_notification_config`, `reporting_webhook`, `artifact_webhook`, `accounts[].notification_configs[]`, and the agent-level notification configuration. `web-bot-auth` selects this profile, and `adcp/webhook-signing/v1` selects the AdCP webhook profile. A registration that omits the field is signed as before: with the AdCP webhook profile, or with the legacy scheme when `authentication` is present. A registration carries `signing_profile` or `authentication`, never both, and the schemas refuse one that carries both. The field has no schema `default`, so a validator or a generated SDK that fills in defaults never adds it next to `authentication`. The seller signs every delivery to that registration with the one selected profile and never signs a delivery both ways. This keeps the mode-selection rule of [Webhook callbacks](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#webhook-callbacks). The two profiles encode binary header values differently: the AdCP webhook profile uses unpadded Base64URL, and this profile uses RFC 8941 Base64. A receiver therefore selects one parser from the registration and never tries both.

A seller advertises the profiles it can sign webhooks with in `webhook_signing.profiles`. A seller that does not list the profile a subscriber selects rejects the configuration as a validation failure.

## Capability

A seller advertises which request-signing profiles it accepts in `request_signing.profiles`, named by the `tag` value each profile puts in `Signature-Input`. Omitting the field means the AdCP RFC 9421 profile only, so every 3.0 to 3.2 capability document stays valid.

```json theme={null}
{
  "request_signing": {
    "supported": true,
    "covers_content_digest": "required",
    "profiles": ["adcp/request-signing/v1", "web-bot-auth"],
    "required_for": ["create_media_buy", "update_media_buy"],
    "supported_for": ["sync_creatives", "create_media_buy", "update_media_buy"]
  },
  "webhook_signing": {
    "supported": true,
    "profile": "adcp/webhook-signing/v1",
    "profiles": ["adcp/webhook-signing/v1", "web-bot-auth"],
    "algorithms": ["ed25519"],
    "legacy_hmac_fallback": false,
    "delivery_retry_horizon_seconds": 86400
  }
}
```

A buyer reads `profiles` and signs with a profile the seller lists. The operation lists (`required_for`, `warn_for`, `supported_for`, and the `protocol_methods_*` lists) keep their meaning for both profiles.

The profile is [experimental](/dist/docs/3.3.0-beta.0/reference/experimental-status): a seller that lists `web-bot-auth` in either list also lists `signing.web_bot_auth` in `experimental_features`.

## Relationship to the AdCP RFC 9421 profile

The two profiles share the signature base, the covered request components, the parameter semantics, the fetch rules, and the signed revocation list. They differ in how a verifier finds the signer's keys and how it reports a failure. The following table lists the mechanisms of the AdCP RFC 9421 profile and the rule that takes their place under this profile.

| AdCP RFC 9421 profile | This profile |
| - | - |
| Tags `adcp/request-signing/v1` and `adcp/webhook-signing/v1` | `tag="web-bot-auth"` |
| Operator-chosen `kid` | JWK thumbprint |
| Keys at `brand.json` `agents[].jwks_uri`, found through [Agent resolution](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#agent-resolution) | Key directory at the identity's origin, found from `Signature-Agent` |
| eTLD+1 binding and the `identity.key_origins` consistency check | Not needed: the identity is the origin, and the directory path is reserved |
| `adcp_use` key purposes | One agent per purpose, each on its own origin |
| "Exactly one `Signature-Input` label" | One `web-bot-auth` signature and one `Signature-Agent` member. `Signature-Input` members with other tags are not read |
| AdCP nonce rules and replay codes | RFC 9421 `nonce` on every signed request, enforced by the seller's verifier |
| `request_signature_*` and `webhook_signature_*` codes in `WWW-Authenticate` | `400`, `403`, or `429`, with `Accept-Signature` added by the AdCP layer; `AUTH_INVALID` for a revoked key; and AdCP authorization errors |
| Separate webhook profile and encoding | Webhooks are ordinary signed requests |

Account authorization, the signed revocation list, and principal boundaries do not change. [AdCP checks after Web Bot Auth verification](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#adcp-checks-after-web-bot-auth-verification) states how the revocation list applies under this profile.

## Differences from the reference library

The conformance tooling judges implementations with Cloudflare's `web-bot-auth` 0.2.0, which implements -00. The profile differs from that library in one way, and AdCP accepts the difference:

* **Member keyed to the label.** The library accepts a signature that covers any one `Signature-Agent` member, whatever its key. The profile requires the member keyed to the signature's label, as section 5.2.1 states: "Its member keyed to the signature label MUST be signed as a component." A verifier built on the library adds this rule through the library's `validate` hook. Scenario `R19` in `scenarios.json` grades it, with tier `adcp`.

The library also does not enforce one WBA MUST yet: section 5.2.1 requires an `https` origin, and the library accepts `http` ([cloudflare/web-bot-auth#162](https://github.com/cloudflare/web-bot-auth/issues/162)). This is a gap in the library, not a difference in the profile. Scenario `R16` grades the rule, with tier `adcp` until the library enforces it. The library does not check the rest of the origin rule either: scenarios `R17` (a path), `R17c` (a trailing `/`), `R17d` (an uppercase host), and `R17e` (an explicit `:443`) grade it, each with tier `adcp`.

## Conformance vectors

The vectors live at `static/compliance/source/test-vectors/wba-profile/` and are served at `/compliance/latest/test-vectors/wba-profile/`. They grade the rules on this page, and each states its expected outcome in the status-code vocabulary of [Error responses](#error-responses).

* `positive/001-single-signer-request`: the request on this page, signed by `https://buyer-7k3q.com`.
* `positive/002-signed-directory-response`: the directory response on this page, signed per Appendix B.1.
* `negative/001-missing-nonce`: `403`.
* `negative/002-replayed-nonce`: `429`; needs preloaded replay state.
* `negative/003-signature-agent-not-covered`: `403`; the header is present but not a covered component.
* `negative/004-key-not-in-named-directory`: `403`; the signing key is published by a different origin than the one `Signature-Agent` names.
* `negative/005-malformed-signature-agent`: `400`; the header does not parse.
* `negative/006-two-signatures`: `403`; the request carries two `web-bot-auth` signatures, each valid on its own.

`scenarios.json` holds failure scenarios as data: a base request, the covered components, parameter overrides, a change after signing, and the expected status. A harness signs each scenario at run time, so no committed signature expires. Each scenario names a tier: `wba` when a WBA library with no AdCP rules already gives the expected outcome, and `adcp` when only the profile's rules do.

The keys in `keys.json` are public test keys with published private seeds. Section 6.8 says test keys "MUST NOT be used in production", and verifiers SHOULD reject them when detected. The README in the vector directory describes the file format and the harness state each vector needs.

## References

* [HTTP Message Signatures for automated traffic, draft-ietf-webbotauth-httpsig-protocol-00](https://datatracker.ietf.org/doc/draft-ietf-webbotauth-httpsig-protocol/00/)
* [HTTP Message Signatures Directory, draft-meunier-http-message-signatures-directory-05](https://datatracker.ietf.org/doc/draft-meunier-http-message-signatures-directory/05/), the "directory draft"
* [Web Bot Auth architecture, draft-meunier-web-bot-auth-architecture](https://datatracker.ietf.org/doc/draft-meunier-web-bot-auth-architecture/)
* [RFC 9421: HTTP Message Signatures](https://www.rfc-editor.org/rfc/rfc9421)
* [RFC 9530: Digest Fields](https://www.rfc-editor.org/rfc/rfc9530)
* [RFC 6454: The Web Origin Concept](https://www.rfc-editor.org/rfc/rfc6454)
* [RFC 7638: JSON Web Key Thumbprint](https://www.rfc-editor.org/rfc/rfc7638) and [RFC 8037](https://www.rfc-editor.org/rfc/rfc8037) Appendix A.3
* [RFC 8941: Structured Field Values for HTTP](https://www.rfc-editor.org/rfc/rfc8941)
* [RFC #7878: An AdCP profile of Web Bot Auth for request signing](https://github.com/adcontextprotocol/adcp/issues/7878)
* [Signed Requests (Transport Layer)](/dist/docs/3.3.0-beta.0/building/by-layer/L1/security#signed-requests-transport-layer), the AdCP RFC 9421 profile
* [Request Signing Guide](/dist/docs/3.3.0-beta.0/building/by-layer/L1/request-signing)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.