# Sign requests with Web Bot Auth

> Identify your agent with Ed25519 HTTP Message Signatures tagged web-bot-auth, register the key, and meet AgentGate's signature rules.

AgentGate verifies [Web Bot Auth](https://datatracker.ietf.org/doc/draft-ietf-webbotauth-httpsig-protocol/)
signatures: Ed25519 HTTP Message Signatures (RFC 9421) tagged
`web-bot-auth`. A verified request is labelled
`agentgate:agent:verified:<name>` and handled by the site's agent policy.

## 1. Get a key registered

`agentgate keygen my-agent` prints a private seed, the key's JWK thumbprint
(the `keyid`) and an `agents.json` entry. There are two ways for a site to
know your public key:

- **Send it to the site operator**, who adds the entry to `agents.json`.
  Local keys are identified by their thumbprint, whatever
  `Signature-Agent` says.
- **Publish a key directory** at
  `https://your-agent.example/.well-known/http-message-signatures-directory`,
  and ask the operator to allow-list your origin under `directories`.
  AgentGate fetches only allow-listed directories.

Directory requirements: the directory media type, `200` only (no
redirects), at most 64 KiB and 32 keys, answered within 5 s from a public
address. `Cache-Control: max-age` is honoured between 5 minutes and 24
hours. Keys match by thumbprint or by the directory's `kid`. A directory
signature (tag `http-message-signatures-directory`) is verified when present
but not required.

## 2. Sign every request

Cover at least `@authority` and the `Signature-Agent` member. Add
`@method`, `@path` and `content-digest` whenever the request does
something; `/agent/submit` requires all of them, and each declared tool's
`auth.components` lists what its endpoint requires.

```http
POST /agent/submit HTTP/1.1
Host: shop.example
Content-Type: application/json
Content-Digest: sha-256=:<base64 SHA-256 of the body>:
Signature-Agent: sig1="https://your-agent.example"
Signature-Input: sig1=("@authority" "@method" "@path" "content-digest" "signature-agent";key="sig1");created=1790000000;expires=1790000300;keyid="<thumbprint>";nonce="<random>";tag="web-bot-auth"
Signature: sig1=:<base64 Ed25519 signature>:

{"id":"order-7f3a-0001","message":"hello"}
```

`Signature-Agent` may be the dictionary form shown
(`sig1="https://…"`, covered as `"signature-agent";key="sig1"`) or the
legacy bare string (covered as `"signature-agent"`).

## 3. Signature rules

| Rule | Detail |
| --- | --- |
| Required parameters | `created`, `expires` and `keyid` |
| Lifetime | `expires` at most 24 hours after `created`; 60 s of clock skew allowed |
| Nonce | optional in general, single use until the signature expires; send a fresh one per request |
| Algorithm | Ed25519 only (RSA-PSS signatures are ignored) |
| Body | on `/agent/submit` and on gateway routes with `requireContentDigest`, `Content-Digest` (RFC 9530, `sha-256`) must match the body |

Failures are labelled and, by default, answered `401`:

| Label | Meaning |
| --- | --- |
| `agentgate:agent:signature:invalid` | bad signature, coverage or parameters |
| `agentgate:agent:signature:expired` | past `expires` |
| `agentgate:agent:signature:replayed` | the nonce was already used |
| `agentgate:agent:signature:unknown_key` | the key is not known for that origin (plus `agentgate:agent:directory:unavailable` when the directory could not be fetched) |
| `agentgate:agent:signature:missing` | no `web-bot-auth` signature |

## Shared key (testing only)

If the operator issued one, the `X-Agent-Key` header also authenticates you
on the agent API (label `agentgate:agent:shared_key`). It is weaker than a
signature and meant for testing.

## Interoperability

The verifier passes the Ed25519 test vectors from Cloudflare's
`web-bot-auth` library and the draft's Appendix E, and verifies the live
directories of ChatGPT agent, Google-Agent, Browserbase and Cloudflare
Browser Run as captured on 2026-09-24. Registries
(draft-meunier-webbotauth-registry) can be imported as extra directory
entries at startup.
