diff --git a/.changeset/docs-rfc9421-signing-guide.md b/.changeset/docs-rfc9421-signing-guide.md
new file mode 100644
index 0000000000..0314cc09ed
--- /dev/null
+++ b/.changeset/docs-rfc9421-signing-guide.md
@@ -0,0 +1,4 @@
+---
+---
+
+Add RFC 9421 request signing guide: new `docs/building/implementation/request-signing.mdx` covering key generation, JWKS/brand.json publication, buyer-side signing, seller-side verification with `requireAuthenticatedOrSigned` + `mcpToolNameResolver`, webhook signing, key rotation, and conformance testing (39 vectors: 12 positive, 27 negative). Adds a Request Signing section to `build-an-agent.mdx` and cross-links from the `security.mdx` quickstart. Ported from adcontextprotocol/adcp-client#914.
diff --git a/docs.json b/docs.json
index a7bdb4f8f7..4504aa800d 100644
--- a/docs.json
+++ b/docs.json
@@ -149,6 +149,7 @@
"docs/building/implementation/mcp-response-extraction",
"docs/building/implementation/a2a-response-extraction",
"docs/building/implementation/security",
+ "docs/building/implementation/request-signing",
"docs/building/implementation/webhook-verifier-tuning",
"docs/building/implementation/seller-integration",
"docs/building/implementation/storyboard-troubleshooting",
diff --git a/docs/building/build-an-agent.mdx b/docs/building/build-an-agent.mdx
index 66000a0c9d..958ad82c9b 100644
--- a/docs/building/build-an-agent.mdx
+++ b/docs/building/build-an-agent.mdx
@@ -188,6 +188,50 @@ Each skill includes variant storyboards for different business models — non-gu
See **[Validate Your Agent](/docs/building/validate-your-agent)** for the full testing workflow — debugging failing steps, running compliance checks, and validating interactively through Addie.
+## Request signing
+
+If your seller agent receives signed requests from buyers, use `requireAuthenticatedOrSigned` to compose signature verification with existing bearer or API key auth. It handles the full matrix: signature-only when headers are present, fallback-only when absent, and `request_signature_required` 401 for unsigned requests on operations listed in `requiredFor`.
+
+```typescript
+import { createAdcpServer, serve } from '@adcp/client';
+import {
+ verifyApiKey,
+ verifySignatureAsAuthenticator,
+ requireAuthenticatedOrSigned,
+ mcpToolNameResolver,
+ MUTATING_TASKS,
+} from '@adcp/client/server';
+import { BrandJsonJwksResolver, InMemoryReplayStore, InMemoryRevocationStore } from '@adcp/client/signing/server';
+
+serve(() => createAdcpServer({ name: 'My Seller', version: '1.0.0', mediaBuy: { /* ... */ } }), {
+ authenticate: requireAuthenticatedOrSigned({
+ signature: verifySignatureAsAuthenticator({
+ capability: { supported: true, required_for: ['create_media_buy'], covers_content_digest: 'either' },
+ jwks: new BrandJsonJwksResolver(),
+ replayStore: new InMemoryReplayStore(),
+ revocationStore: new InMemoryRevocationStore(),
+ resolveOperation: mcpToolNameResolver,
+ }),
+ fallback: verifyApiKey({ keys: { /* ... */ } }),
+ requiredFor: [...MUTATING_TASKS],
+ resolveOperation: mcpToolNameResolver,
+ }),
+});
+```
+
+For outbound webhook signing, pass a `signerKey` to `createAdcpServer`:
+
+```typescript
+createAdcpServer({
+ webhooks: {
+ signerKey: { keyid: 'my-webhook-key-2026', alg: 'ed25519', privateKey: webhookPrivateJwk },
+ },
+ // ...
+});
+```
+
+See **[Request Signing Guide](/docs/building/implementation/request-signing)** for the full walkthrough: key generation, JWKS publication, brand.json setup, buyer-side signing, and conformance testing.
+
## Additional resources
The JS/TS SDK includes documentation designed for both humans and coding agents:
diff --git a/docs/building/implementation/request-signing.mdx b/docs/building/implementation/request-signing.mdx
new file mode 100644
index 0000000000..ed3675c572
--- /dev/null
+++ b/docs/building/implementation/request-signing.mdx
@@ -0,0 +1,398 @@
+---
+title: Request Signing Guide
+description: "Step-by-step guide to RFC 9421 request signing in AdCP: key generation, JWKS publication, brand.json setup, client-side signing, server-side verification, webhook signing, key rotation, and conformance testing."
+"og:title": "AdCP — Request Signing Guide"
+---
+
+AdCP 3.0 supports [HTTP Message Signatures (RFC 9421)](https://www.rfc-editor.org/rfc/rfc9421) for cryptographic request authentication. A buyer signs outbound requests so the seller can verify who sent them and that the payload wasn't tampered with. A seller signs outbound webhooks so the buyer can verify authenticity.
+
+Signing is **optional in AdCP 3.0** and becomes **mandatory in AdCP 4.0** for all spend-committing operations. Agents that don't sign yet must still tolerate signature headers (`Signature`, `Signature-Input`, `Content-Digest`) on inbound requests without breaking.
+
+
+This is the practical implementation guide. For the normative specification — covered components, canonicalization rules, the full verifier checklist, replay dedup sizing, and the complete error taxonomy — see [Security: Signed Requests](/docs/building/implementation/security#signed-requests-transport-layer).
+
+
+## When you need this
+
+| You are a... | You need to... | Why |
+|---|---|---|
+| **Buyer** (calls seller tools) | Sign outbound requests | Sellers may require proof the request came from you |
+| **Buyer** (receives webhooks) | Verify inbound webhook signatures | Confirm the webhook came from the seller |
+| **Seller** (receives tool calls) | Verify inbound request signatures | Confirm the buyer is who they claim to be |
+| **Seller** (sends webhooks) | Sign outbound webhooks | Let buyers verify webhook authenticity |
+| **Orchestrator** (proxies to sellers) | Sign outbound requests + verify inbound webhooks | You're the buyer from the seller's perspective |
+
+## Key concepts
+
+### Signature coverage
+
+The AdCP signing profile covers these request components:
+
+- `@method` — HTTP method
+- `@target-uri` — full canonicalized request URL
+- `@authority` — lowercased host header
+- `content-type` — media type
+- `content-digest` — SHA-256 or SHA-512 hash of the request body (see `covers_content_digest` capability)
+
+If any covered component changes after signing, verification fails.
+
+### Key separation
+
+Every agent needs **separate keys per purpose**, each with a distinct `kid` and `adcp_use` tag:
+
+- `adcp_use: "request-signing"` — for signing outbound tool calls
+- `adcp_use: "webhook-signing"` — for signing outbound webhooks
+
+Reusing a key across purposes is forbidden by the spec.
+
+### Discovery chain
+
+Verifiers find your public key through a three-step chain:
+
+```
+Your domain (e.g., agent.example.com)
+ -> /.well-known/brand.json # brand manifest with agent declarations
+ -> agents[].jwks_uri # pointer to your key store
+ -> /.well-known/jwks.json # JSON Web Key Set with public keys
+```
+
+The `@adcp/client` SDK provides `BrandJsonJwksResolver` which handles this chain automatically with caching and refresh.
+
+## Step 1: Generate a signing key
+
+### CLI
+
+```bash
+adcp signing generate-key --alg ed25519 --kid my-agent-2026 \
+ --private-out ./private.jwk --public-out ./public-jwks.json
+```
+
+This generates an Ed25519 keypair and writes:
+- `private.jwk` — the private key (JWK with `d` field). Keep this secret.
+- `public-jwks.json` — the public key in JWKS format. Publish this.
+
+### Programmatic (TypeScript)
+
+```typescript
+import { generateKeyPair, exportJWK } from 'jose';
+
+const { publicKey, privateKey } = await generateKeyPair('EdDSA', { crv: 'Ed25519' });
+const publicJwk = await exportJWK(publicKey);
+const privateJwk = await exportJWK(privateKey);
+
+const kid = 'my-agent-2026';
+publicJwk.kid = kid;
+publicJwk.use = 'sig';
+publicJwk.key_ops = ['verify'];
+publicJwk.adcp_use = 'request-signing';
+```
+
+### Supported algorithms
+
+| Algorithm | `alg` value | Key type | Notes |
+|---|---|---|---|
+| Ed25519 | `ed25519` (RFC 9421) / `EdDSA` (JWK) | `OKP` / `Ed25519` | Preferred. Fast, small signatures. |
+| ECDSA P-256 | `ecdsa-p256-sha256` (RFC 9421) / `ES256` (JWK) | `EC` / `P-256` | Edge-runtime friendly (Cloudflare Workers, Vercel Edge). |
+
+
+The algorithm name differs between the JWK entry (`"alg": "EdDSA"`) and the RFC 9421 `Signature-Input` parameter (`alg="ed25519"`). See the [algorithm naming table](/docs/building/implementation/security#adcp-rfc-9421-profile) in the spec.
+
+
+### Storing the private key
+
+- **Environment variable**: `ADCP_SIGNING_PRIVATE_KEY='{"kid":"...","kty":"OKP",...}'`
+- **Secret manager** (GCP Secret Manager, AWS Secrets Manager, etc.): load at boot, keep in memory for the process lifetime
+- **File**: for development only. Never commit to version control.
+
+## Step 2: Publish your public keys
+
+### JWKS endpoint
+
+Serve a JSON Web Key Set at a stable HTTPS URL (defaults to `/.well-known/jwks.json`):
+
+```json
+{
+ "keys": [
+ {
+ "kid": "my-agent-2026",
+ "kty": "OKP",
+ "crv": "Ed25519",
+ "x": "",
+ "use": "sig",
+ "key_ops": ["verify"],
+ "adcp_use": "request-signing"
+ }
+ ]
+}
+```
+
+Only public keys go here — no `d` field. Set `Cache-Control: max-age=3600` or similar. If you serve both request-signing and webhook-signing keys, include both in the same JWKS with different `kid` values and `adcp_use` tags.
+
+### brand.json
+
+Serve at `/.well-known/brand.json` on your brand domain. The `jwks_uri` is how verifiers find your keys:
+
+```json
+{
+ "name": "My Company",
+ "domain": "example.com",
+ "agents": [
+ {
+ "url": "https://agent.example.com",
+ "jwks_uri": "https://agent.example.com/.well-known/jwks.json",
+ "capabilities": ["media-buy"],
+ "adcp_use": ["request-signing"]
+ }
+ ]
+}
+```
+
+## Step 3: Sign outbound requests (buyer / orchestrator)
+
+### Wrapping fetch
+
+`createSigningFetch` wraps any `fetch`-compatible function to sign outbound requests automatically:
+
+```typescript
+import { createSigningFetch } from '@adcp/client/signing';
+
+const privateJwk = JSON.parse(process.env.ADCP_SIGNING_PRIVATE_KEY);
+
+const signingFetch = createSigningFetch(fetch, {
+ keyid: 'my-agent-2026',
+ alg: 'ed25519',
+ privateKey: privateJwk,
+});
+
+// Use signingFetch anywhere you'd use fetch.
+// Signature, Signature-Input, and Content-Digest headers are added automatically.
+await signingFetch('https://seller.example.com/mcp', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(payload),
+});
+```
+
+### Capability-aware signing
+
+`buildAgentSigningFetch` checks whether the target seller supports `signed-requests` and only signs when supported. This is the recommended approach for production:
+
+```typescript
+import { buildAgentSigningFetch, CapabilityCache } from '@adcp/client/signing/client';
+
+const capabilityCache = new CapabilityCache();
+
+const signingFetch = buildAgentSigningFetch({
+ upstream: fetch,
+ signing: {
+ kid: 'my-agent-2026',
+ alg: 'ed25519',
+ private_key: privateJwk,
+ agent_url: 'https://agent.example.com',
+ sign_supported: true,
+ },
+ getCapability: () => capabilityCache.get('https://seller.example.com'),
+});
+```
+
+This avoids sending signatures to agents that don't expect them and caches capability lookups.
+
+## Step 4: Verify inbound signatures (seller)
+
+### Express middleware
+
+For raw Express routes, mount `createExpressVerifier` after a raw-body middleware. Use `mcpToolNameResolver` for the `resolveOperation` callback — it parses the JSON-RPC envelope and returns the MCP tool name:
+
+```typescript
+import {
+ createExpressVerifier,
+ StaticJwksResolver,
+ InMemoryReplayStore,
+ InMemoryRevocationStore,
+} from '@adcp/client/signing';
+import { mcpToolNameResolver } from '@adcp/client/server';
+
+app.post(
+ '/mcp',
+ rawBodyMiddleware(), // req.rawBody must hold the byte-exact body
+ createExpressVerifier({
+ capability: {
+ supported: true,
+ covers_content_digest: 'required',
+ required_for: ['create_media_buy', 'update_media_buy'],
+ },
+ jwks: new StaticJwksResolver(buyerPublicKeys),
+ replayStore: new InMemoryReplayStore(),
+ revocationStore: new InMemoryRevocationStore(),
+ resolveOperation: mcpToolNameResolver,
+ }),
+ handler
+);
+// On verify: req.verifiedSigner = { keyid, agent_url?, verified_at }.
+// On reject: 401 with WWW-Authenticate: Signature error="".
+```
+
+### Composing signature + bearer auth with `requireAuthenticatedOrSigned`
+
+`requireAuthenticatedOrSigned` bundles the full composition: presence-gated routing (signature auth when headers present, fallback otherwise) plus `requiredFor` enforcement — unauthenticated requests for signing-required operations get `401 request_signature_required` even when no credentials at all are supplied.
+
+```typescript
+import {
+ serve,
+ verifyApiKey,
+ verifySignatureAsAuthenticator,
+ requireAuthenticatedOrSigned,
+ mcpToolNameResolver,
+ MUTATING_TASKS,
+} from '@adcp/client/server';
+import { BrandJsonJwksResolver, InMemoryReplayStore, InMemoryRevocationStore } from '@adcp/client/signing/server';
+
+serve(createAgent, {
+ authenticate: requireAuthenticatedOrSigned({
+ signature: verifySignatureAsAuthenticator({
+ capability: { supported: true, required_for: ['create_media_buy'], covers_content_digest: 'either' },
+ jwks: new BrandJsonJwksResolver(),
+ replayStore: new InMemoryReplayStore(),
+ revocationStore: new InMemoryRevocationStore(),
+ resolveOperation: mcpToolNameResolver,
+ }),
+ fallback: verifyApiKey({ keys: { 'sk_live_abc': { principal: 'acct_42' } } }),
+ requiredFor: [...MUTATING_TASKS],
+ resolveOperation: mcpToolNameResolver,
+ }),
+});
+```
+
+`MUTATING_TASKS` is the full list of spend-committing and state-changing operations exported from `@adcp/client/server` — use it rather than maintaining your own list.
+
+### JWKS resolver options
+
+| Resolver | Use case |
+|---|---|
+| `StaticJwksResolver` | Fixed set of known buyer keys. Good for dev/testing. |
+| `HttpsJwksResolver` | Fetches JWKS from a URL with caching and refresh. |
+| `BrandJsonJwksResolver` | Full discovery chain: brand.json → jwks_uri → JWKS. Recommended for production. |
+
+## Step 5: Verify inbound webhooks (buyer / orchestrator)
+
+When sellers send webhooks, verify the signature to confirm authenticity:
+
+```typescript
+import {
+ verifyWebhookSignature,
+ BrandJsonJwksResolver,
+ InMemoryReplayStore,
+} from '@adcp/client/signing/server';
+
+const jwks = new BrandJsonJwksResolver();
+const replayStore = new InMemoryReplayStore();
+
+app.post('/webhook', async (req, res) => {
+ try {
+ await verifyWebhookSignature(req, { jwks, replayStore });
+ } catch {
+ return res.status(401).json({ error: 'invalid webhook signature' });
+ }
+
+ // Process the verified webhook...
+});
+```
+
+## Step 6: Sign outbound webhooks (seller)
+
+Pass a `signerKey` to `createAdcpServer` and the framework signs every outbound webhook automatically:
+
+```typescript
+serve(() => createAdcpServer({
+ name: 'My Seller',
+ version: '1.0.0',
+ webhooks: {
+ signerKey: {
+ keyid: 'my-seller-webhook-2026',
+ alg: 'ed25519',
+ privateKey: webhookPrivateJwk,
+ },
+ },
+ mediaBuy: { /* ... */ },
+}));
+```
+
+Publish a separate JWK with `"adcp_use": "webhook-signing"` in your JWKS alongside your request-signing key.
+
+## Step 7: Declare the capability
+
+If your seller verifies inbound signatures, declare `signed_requests` in your capabilities so buyers know to sign:
+
+```typescript
+createAdcpServer({
+ capabilities: {
+ overrides: {
+ signed_requests: {
+ supported: true,
+ required_for: ['create_media_buy', 'update_media_buy'],
+ supported_for: ['sync_creatives', 'sync_audiences'],
+ covers_content_digest: 'either',
+ },
+ },
+ },
+ mediaBuy: { /* ... */ },
+});
+```
+
+Buyers call `get_adcp_capabilities` and read `request_signing.required_for` and `supported_for` to know which operations you expect them to sign.
+
+## Key rotation
+
+The JWKS endpoint supports multiple keys simultaneously for zero-downtime rotation:
+
+1. Generate a new keypair with a new `kid`
+2. Add the new public key to JWKS (both old and new are published)
+3. Update signing configuration to use the new private key
+4. After 24–48 hours, remove the old public key from JWKS
+
+For emergency rotation (key compromise), add the old `kid` to `revoked_kids` in your revocation list and rotate to a new key immediately. See [Revocation](/docs/building/implementation/security#revocation) for the revocation list format.
+
+## Testing
+
+### Conformance vectors
+
+The spec ships **39 test vectors** at `compliance/cache/3.0.0/test-vectors/request-signing/` (source at `static/compliance/source/test-vectors/request-signing/`):
+
+- **12 positive vectors**: valid signed requests your verifier must accept (non-4xx)
+- **27 negative vectors**: invalid requests your verifier must reject with `401` and the correct error code
+
+```bash
+# Debug a single vector
+adcp signing verify-vector \
+ --vector compliance/cache/3.0.0/test-vectors/request-signing/positive/001-basic-post.json
+```
+
+### Grade your verifier
+
+```bash
+adcp grade request-signing https://agent.example.com/mcp --auth-token $TOKEN
+```
+
+### Error codes
+
+When verification fails, return `401` with `WWW-Authenticate: Signature error=""`:
+
+| Code | Meaning |
+|---|---|
+| `missing_signature` | Signature headers not present when required |
+| `invalid_signature` | Signature doesn't verify against the public key |
+| `expired_signature` | Signature timestamp too old |
+| `replayed_nonce` | Nonce was already used |
+| `revoked_key` | Key has been revoked |
+| `unknown_key` | Key ID not found in JWKS |
+| `unsupported_algorithm` | Algorithm not in allowlist |
+
+For the full error code taxonomy, see [Transport error taxonomy](/docs/building/implementation/security#transport-error-taxonomy).
+
+## Related
+
+- [Security: Signed Requests](/docs/building/implementation/security#signed-requests-transport-layer) — normative spec with verifier checklist, canonicalization rules, and replay dedup sizing
+- [Push Notifications](/docs/building/implementation/webhooks) — webhook setup including signature verification
+- [Validate Your Agent](/docs/building/validate-your-agent) — full compliance validation including signing conformance
+- [Build an Agent](/docs/building/build-an-agent) — SDK setup and storyboard validation
+- [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421) — HTTP Message Signatures specification
diff --git a/docs/building/implementation/security.mdx b/docs/building/implementation/security.mdx
index 44d7a1d4af..47a2b2d08f 100644
--- a/docs/building/implementation/security.mdx
+++ b/docs/building/implementation/security.mdx
@@ -795,6 +795,10 @@ Read calls remain bearer-authenticated. Signing read traffic adds verification c
#### Quickstart: opt into request signing in 3.0
+
+Looking for step-by-step setup with code examples? See the **[Request Signing Guide](/docs/building/implementation/request-signing)** — key generation, JWKS publication, brand.json, SDK signing/verification patterns, and conformance testing. This section is the normative specification that guide implements.
+
+
For implementers who want to pilot signing in 3.0 before the 4.0 flip:
**As an agent that signs requests:**