> ## Documentation Index
> Fetch the complete documentation index at: https://adcp-docs-ja.pier1.co.jp/llms.txt
> Use this file to discover all available pages before exploring further.

# リクエスト署名ガイド

> AdCP における RFC 9421 リクエスト署名のステップバイステップガイド: 鍵生成、JWKS 公開、brand.json セットアップ、クライアント側署名、サーバー側検証、webhook 署名、鍵ローテーション、適合性テスト。

AdCP 3.0 は暗号的リクエスト認証のため [HTTP Message Signatures (RFC 9421)](https://www.rfc-editor.org/rfc/rfc9421) をサポートします。バイヤーはアウトバウンドリクエストに署名し、セラーが誰が送ったかとペイロードが改ざんされていないことを検証できます。セラーはアウトバウンド webhook に署名し、バイヤーが真正性を検証できます。

署名は **AdCP 3.0 ではオプション** で、すべての支出コミット操作について **AdCP 4.0 で必須** になります。まだ署名しないエージェントも、インバウンドリクエストの署名ヘッダー（`Signature`、`Signature-Input`、`Content-Digest`）を壊れることなく許容しなければなりません。

<Note>
  これは実践的実装ガイドです。規範的仕様 — カバードコンポーネント、正準化ルール、完全な検証者チェックリスト、リプレイ dedup サイジング、完全なエラー分類 — については [Security: Signed Requests](/docs/building/by-layer/L1/security#署名付きリクエストトランスポート層) を参照してください。
</Note>

<Info>
  下のコード例はタブで **JavaScript/TypeScript**、**Python**、**Go** SDK ヘルパーを使います。3 つの SDK すべてが同じ適合性ベクターに対して同じ RFC 9421 プロファイルを実装します — API 表面は異なりますがワイヤー出力は同一です。あなたの言語がリストされていない場合、[適合性ベクター](#testing) と [規範的仕様](/docs/building/by-layer/L1/security#署名付きリクエストトランスポート層) は言語非依存です。
</Info>

## これが必要になるとき

| あなたは…                   | 必要なこと…                               | なぜ                         |
| ----------------------- | ------------------------------------ | -------------------------- |
| **バイヤー**（セラーツールを呼ぶ）     | アウトバウンドリクエストに署名                      | セラーはリクエストがあなたから来た証明を要求するかも |
| **バイヤー**（webhook を受信）   | インバウンド webhook 署名を検証                 | webhook がセラーから来たことを確認      |
| **セラー**（ツール呼び出しを受信）     | インバウンドリクエスト署名を検証                     | バイヤーが主張する者であることを確認         |
| **セラー**（webhook を送信）    | アウトバウンド webhook に署名                  | バイヤーが webhook 真正性を検証できるように |
| **オーケストレーター**（セラーにプロキシ） | アウトバウンドリクエストに署名 + インバウンド webhook を検証 | セラーの視点ではあなたがバイヤー           |

## 主要概念

### 署名カバレッジ

AdCP 署名プロファイルは以下のリクエストコンポーネントをカバーします:

* `@method` — HTTP メソッド
* `@target-uri` — 完全な正準化リクエスト URL
* `@authority` — 小文字化されたホストヘッダー
* `content-type` — メディアタイプ
* `content-digest` — リクエストボディの SHA-256 または SHA-512 ハッシュ（`covers_content_digest` ケイパビリティを参照）

任意のカバードコンポーネントが署名後に変わると、検証が失敗します。

### 鍵分離

すべてのエージェントは、明確な `kid` と `adcp_use` タグを持つ署名鍵を公開します:

* `adcp_use: "request-signing"` — アウトバウンドツール呼び出しとアウトバウンド webhook の署名用
* `adcp_use: "webhook-signing"` — 非推奨。後方互換性のため依然として webhook パスで受理される

爆発半径分離のため、ツール呼び出しと webhook に同じ鍵素材を再利用するのではなく、webhook 配信用に別の `kid` の下で 2 つ目の `request-signing` 鍵を公開してください。

### ディスカバリーチェーン

検証者は 3 ステップのチェーンを通じてあなたの公開鍵を見つけます:

```
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
```

`@adcp/client` SDK は、キャッシングとリフレッシュでこのチェーンを自動的に処理する `BrandJsonJwksResolver` を提供します。

## ステップ 1: 署名鍵を生成する

### CLI

```bash theme={null}
adcp signing generate-key --alg ed25519 --kid my-agent-2026 \
  --private-out ./private.jwk --public-out ./public-jwks.json
```

これは Ed25519 キーペアを生成し、以下を書き込みます:

* `private.jwk` — 秘密鍵（`d` フィールドを持つ JWK）。これを秘密に保つ。
* `public-jwks.json` — JWKS 形式の公開鍵。これを公開する。

### プログラマティック

<Tabs>
  <Tab title="JavaScript/TypeScript">
    ```typescript theme={null}
    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';
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from adcp.signing import generate_signing_keypair

    # CLI equivalence: adcp-keygen --alg ed25519 --purpose request-signing --kid my-agent-2026
    pem_bytes, public_jwk = generate_signing_keypair(
        alg="ed25519",
        purpose="request-signing",
        kid="my-agent-2026",
    )
    # pem_bytes — write to disk with mode 0600 and O_EXCL, or pass to a secret manager.
    # public_jwk — publish in your JWKS endpoint. Already includes kid, use, key_ops, and adcp_use.
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import "github.com/adcontextprotocol/adcp-go/adcp/signing"

    res, err := signing.GenerateKeyForProfile(
        signing.AlgEd25519,
        "my-agent-2026",
        signing.ProfileRequestSigning,
    )
    if err != nil { /* handle */ }
    // res.PrivateKeyPEM — write to disk with mode 0600, or pass to a secret manager.
    // res.PublicJWK    — serialize and publish in your JWKS endpoint. AdCP-required
    //                    fields (kid, kty, crv, alg, use, key_ops, adcp_use) are set.
    ```

    webhook RFC 9421 プロファイルには `signing.ProfileWebhookSigning` を使います。対応する公開鍵を `adcp_use: "request-signing"` で公開してください。webhook 専用の鍵素材が欲しい場合、別の `kid` を使ってください。
  </Tab>
</Tabs>

### サポートされるアルゴリズム

| Algorithm   | `alg` value                                    | Key type          | Notes                                           |
| ----------- | ---------------------------------------------- | ----------------- | ----------------------------------------------- |
| Ed25519     | `ed25519` (RFC 9421) / `EdDSA` (JWK)           | `OKP` / `Ed25519` | 推奨。高速、小さい署名。                                    |
| ECDSA P-256 | `ecdsa-p256-sha256` (RFC 9421) / `ES256` (JWK) | `EC` / `P-256`    | エッジランタイムフレンドリー（Cloudflare Workers、Vercel Edge）。 |

<Warning>
  アルゴリズム名は JWK エントリー（`"alg": "EdDSA"`）と RFC 9421 `Signature-Input` パラメーター（`alg="ed25519"`）で異なります。仕様の [アルゴリズム命名テーブル](/docs/building/by-layer/L1/security#adcp-rfc-9421-profile) を参照してください。
</Warning>

### 秘密鍵の保存

あなたのランタイムがサポートする最も強いオプションを選んでください。最も安全なものから最も安全でないものへ:

* **クラウド KMS**（GCP Cloud KMS、AWS KMS、Azure Key Vault）: 秘密鍵は HSM 内で生成され、決してそれを離れません。署名は KMS API を呼ぶことで実行されます。あなたは鍵バイトではなく JWK 参照のみを保持します。TypeScript SDK は GCP Cloud KMS 用に `createKmsSigner` を公開します — [`@adcp/client/signing/kms`](https://github.com/adcontextprotocol/adcp-client/tree/main/src/lib/signing/kms) を参照。支出コミット操作を処理する任意のエージェントに推奨。
* **シークレットマネージャー**（GCP Secret Manager、AWS Secrets Manager、HashiCorp Vault）: ブート時にロードし、プロセスライフタイムの間メモリに保つ。KMS より簡単だが鍵素材がプロセス内に存在する — メモリダンプ、ロギング、または侵害された依存関係を通じてリークする。
* **環境変数**: `ADCP_SIGNING_PRIVATE_KEY='{"kid":"...","kty":"OKP",...}'`。開発と小規模デプロイに許容可能。シークレットマネージャーと同じメモリ常駐リスク。
* **ファイル**: 開発のみ。決してバージョン管理にコミットしない。既存ファイルが決して上書きされないようモード `0600` と `O_EXCL` を使う — `Path.write_bytes` はプロセス umask（しばしば `0644`、world-readable）を継承し、秘密鍵素材には安全でない。

<Note>
  KMS を選ぶと、署名レイテンシーが上がります（リクエストごとに HSM への 1 ラウンドトリップ）。コミットする前に負荷下でプロファイルしてください — TypeScript と Python SDK は JWK メタデータを積極的にキャッシュし、内部テストで GCP KMS に対して毎秒数百の署名を維持できますが、あなたの数字はリージョンと並行性に依存します。
</Note>

## ステップ 2: 公開鍵を公開する

### JWKS エンドポイント

安定した HTTPS URL（デフォルトは `/.well-known/jwks.json`）で JSON Web Key Set をサーブします:

```json theme={null}
{
  "keys": [
    {
      "kid": "my-agent-2026",
      "kty": "OKP",
      "crv": "Ed25519",
      "x": "<base64url-encoded-public-key>",
      "use": "sig",
      "key_ops": ["verify"],
      "adcp_use": "request-signing"
    }
  ]
}
```

ここには公開鍵のみ — `d` フィールドなし。`Cache-Control: max-age=3600` または同様を設定してください。webhook 配信に別の鍵素材を使う場合、明確な `kid` を持つ 2 つ目の `request-signing` JWK を公開してください。非推奨の `webhook-signing` JWK は後方互換性のため webhook パスで受理されたままです。

### brand.json

あなたのブランドドメインの `/.well-known/brand.json` でサーブします。`jwks_uri` は検証者があなたの鍵を見つける方法です:

```json theme={null}
{
  "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"]
    }
  ]
}
```

## ステップ 3: アウトバウンドリクエストに署名する（バイヤー / オーケストレーター）

### fetch / HTTP クライアントのラッピング

<Tabs>
  <Tab title="JavaScript/TypeScript">
    `createSigningFetch` は任意の `fetch` 互換関数をラップしてアウトバウンドリクエストに自動的に署名します:

    ```typescript theme={null}
    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),
    });
    ```
  </Tab>

  <Tab title="Python">
    Python SDK は、`ADCPClient` で `signing` が設定されているときすべてのアウトバウンドリクエストに自動署名します:

    ```python theme={null}
    from adcp import ADCPClient
    from adcp.signing import SigningConfig, load_private_key_pem

    private_key = load_private_key_pem(open("private-key.pem", "rb").read())

    client = ADCPClient(
        base_url="https://seller.example.com/mcp",
        signing=SigningConfig(
            key_id="my-agent-2026",
            alg="ed25519",
            private_key=private_key,
            cover_content_digest=True,
        ),
    )
    # Every request the client sends carries Signature, Signature-Input, and
    # Content-Digest headers.
    ```

    より低レベルの制御（例: クライアント外で任意の `httpx.Request` に署名）には、`sign_request` を直接呼びます:

    ```python theme={null}
    from adcp.signing import sign_request

    signed = sign_request(
        method="POST",
        url="https://seller.example.com/mcp",
        headers={"Content-Type": "application/json"},
        body=request_body_bytes,
        private_key=private_key,
        key_id="my-agent-2026",
        alg="ed25519",
        cover_content_digest=True,
    )
    # signed.as_dict() returns the headers to attach to the outgoing request.
    ```
  </Tab>

  <Tab title="Go">
    Go SDK は、`http.Client` トランスポートをラップする `Signer` を公開します:

    ```go theme={null}
    import (
        "net/http"
        "github.com/adcontextprotocol/adcp-go/adcp/signing"
    )

    priv, _, _ := signing.LoadPrivateKey(pemBytes)
    signer, _ := signing.NewSigner(signing.SignerOptions{
        KeyID:      "my-agent-2026",
        Algorithm:  signing.AlgEd25519,
        PrivateKey: priv,
    })

    client := &http.Client{
        Transport: signer.RoundTripper(http.DefaultTransport, true /* cover content-digest */),
    }

    req, _ := http.NewRequest("POST", "https://seller.example.com/mcp", body)
    req.Header.Set("Content-Type", "application/json")
    resp, _ := client.Do(req)
    // Signature, Signature-Input, and Content-Digest are added by the transport.
    ```

    トランスポートなしの単発署名には、リクエスト上で `signer.SignRequest(req, signing.SignOptions{CoverContentDigest: true})` を直接呼びます。
  </Tab>
</Tabs>

### ケイパビリティ対応署名

`buildAgentSigningFetch` はターゲットセラーが `signed-requests` をサポートするかをチェックし、サポートされるときのみ署名します。これは本番の推奨アプローチです:

```typescript theme={null}
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'),
});
```

これは署名を期待しないエージェントへの署名送信を避け、ケイパビリティルックアップをキャッシュします。

## ステップ 4: インバウンド署名を検証する（セラー）

### フレームワークミドルウェア

<Tabs>
  <Tab title="JavaScript/TypeScript">
    生の Express ルートには、raw-body ミドルウェアの後に `createExpressVerifier` をマウントします。`resolveOperation` コールバックには `mcpToolNameResolver` を使います — JSON-RPC エンベロープを解析し MCP ツール名を返します:

    ```typescript theme={null}
    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="<code>".
    ```
  </Tab>

  <Tab title="Python">
    Python SDK は Flask と Starlette/FastAPI 用のフレームワークラッパーを出荷します。両方とも同じ `verify_request_signature` を呼び、拒否時に `SignatureVerificationError` を発生させます — それを `unauthorized_response_headers` を持つ 401 にマップします:

    ```python theme={null}
    from fastapi import FastAPI, Request, HTTPException
    from adcp.signing import (
        VerifyOptions,
        VerifierCapability,
        CachingJwksResolver,
        InMemoryReplayStore,
        StaticRevocationChecker,
    )
    from adcp.signing.middleware import (
        verify_starlette_request,
        unauthorized_response_headers,
    )
    from adcp.signing.errors import SignatureVerificationError

    app = FastAPI()

    verify_options = VerifyOptions(
        capability=VerifierCapability(
            supported=True,
            covers_content_digest="required",
            required_for={"create_media_buy", "update_media_buy"},
        ),
        jwks_resolver=CachingJwksResolver(),
        replay_store=InMemoryReplayStore(),
        revocation_checker=StaticRevocationChecker(set()),
    )

    @app.post("/mcp")
    async def mcp(request: Request):
        try:
            signer = await verify_starlette_request(request, options=verify_options)
        except SignatureVerificationError as exc:
            raise HTTPException(
                status_code=401,
                detail=exc.code,
                headers=unauthorized_response_headers(exc),
            )
        # signer.key_id, signer.agent_url, signer.verified_at available for audit.
        body = await request.body()
        return await handle_mcp(body, signer)
    ```

    Flask には: `verify_starlette_request` を `verify_flask_request`（同期）に置き換えます。非 Starlette ASGI フレームワークには、`verify_request_signature` を直接呼びます。
  </Tab>

  <Tab title="Go">
    `http.Handler` チェーンに `signing.Middleware` をマウントします。ミドルウェアはインバウンド署名を検証し、成功時にリクエストコンテキストに `VerifiedSigner` を投入し、失敗時に `WWW-Authenticate: Signature error="<code>"` を伴う `401` を書き込みます:

    ```go theme={null}
    import (
        "net/http"
        "github.com/adcontextprotocol/adcp-go/adcp/signing"
    )

    resolver := signing.NewCachingJWKSResolver()
    replay := signing.NewMemoryReplayStore(0 /* default cap */)
    revocation := signing.NewStaticRevocationSource(nil)

    mw := signing.Middleware(signing.MiddlewareOptions{
        Resolver:            resolver,
        Replay:              replay,
        Revocation:          revocation,
        OperationResolver:   signing.DefaultOperationResolver, // /adcp/<op>
        ContentDigestPolicy: signing.DigestRequired,
        RequiredFor:         []string{"create_media_buy", "update_media_buy"},
    })

    http.Handle("/mcp", mw(handler))

    // Inside handler:
    func handler(w http.ResponseWriter, r *http.Request) {
        v := signing.VerifiedSignerFromContext(r.Context())
        if v == nil {
            // Operation not in RequiredFor and request was unsigned — proceed
            // with bearer auth or whatever fallback you've configured.
        }
        // v.KeyID, v.AgentURL, v.VerifiedAt, v.Algorithm — available for audit.
    }
    ```

    MCP サーバーには: `signing.DefaultOperationResolver` を、JSON-RPC エンベロープを解析し MCP ツール名を返すカスタムリゾルバー（TS SDK の `mcpToolNameResolver` の同等物）に置き換えます。
  </Tab>
</Tabs>

### `requireAuthenticatedOrSigned` で署名 + bearer 認証を合成する

`requireAuthenticatedOrSigned` は完全な合成をバンドルします: presence ゲートルーティング（ヘッダー存在時は署名認証、それ以外はフォールバック）と `requiredFor` 強制 — 署名必須操作の未認証リクエストは、認証情報がまったく供給されなくても `401 request_signature_required` を得ます。

```typescript theme={null}
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` は `@adcp/client/server` からエクスポートされる支出コミットと状態変更操作の完全なリストです — 自身のリストを保守するのではなくそれを使ってください。

### JWKS リゾルバーオプション

| Resolver                | Use case                                             |
| ----------------------- | ---------------------------------------------------- |
| `StaticJwksResolver`    | 既知のバイヤー鍵の固定セット。開発/テストに良い。                            |
| `HttpsJwksResolver`     | キャッシングとリフレッシュで URL から JWKS を取得。                      |
| `BrandJsonJwksResolver` | 完全なディスカバリーチェーン: brand.json → jwks\_uri → JWKS。本番に推奨。 |

## ステップ 5: インバウンド webhook を検証する（バイヤー / オーケストレーター）

セラーが webhook を送るとき、真正性を確認するため署名を検証してください。webhook プロファイルはリクエスト署名と同じ RFC 9421 メカニクスを使いますが、`tag="adcp/webhook-signing/v1"` と `Content-Digest` が常にカバーされます（オプトアウトなし）。

<Tabs>
  <Tab title="JavaScript/TypeScript">
    ```typescript theme={null}
    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...
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from adcp.signing import (
        WebhookVerifyOptions,
        BrandJsonJwksResolver,
        InMemoryReplayStore,
        verify_webhook_signature,
    )
    from adcp.signing.errors import SignatureVerificationError

    webhook_options = WebhookVerifyOptions(
        jwks_resolver=BrandJsonJwksResolver(),
        replay_store=InMemoryReplayStore(),
    )

    @app.post("/webhook")
    async def webhook(request: Request):
        body = await request.body()
        try:
            sender = verify_webhook_signature(
                method=request.method,
                url=str(request.url),
                headers=dict(request.headers),
                body=body,
                options=webhook_options,
            )
        except SignatureVerificationError:
            raise HTTPException(status_code=401, detail="invalid webhook signature")
        # sender.key_id, sender.agent_url available for audit; process the webhook.
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
        "github.com/adcontextprotocol/adcp-go/adcp/signing"
    )

    // Mount the same Middleware on your webhook receiver, but configure it for
    // the webhook profile — adcp_use="request-signing" (deprecated
    // "webhook-signing" also accepted), Content-Digest required, no required_for
    // gating (webhooks always carry signatures).
    webhookMW := signing.Middleware(signing.MiddlewareOptions{
        Resolver:            signing.NewBrandJSONJWKSResolver(),
        Replay:              signing.NewMemoryReplayStore(0),
        Revocation:          signing.NewStaticRevocationSource(nil),
        Profile:             signing.ProfileWebhookSigning,
        ContentDigestPolicy: signing.DigestRequired,
    })

    http.Handle("/webhook", webhookMW(webhookHandler))

    func webhookHandler(w http.ResponseWriter, r *http.Request) {
        sender := signing.VerifiedSignerFromContext(r.Context())
        // sender.KeyID, sender.AgentURL — process the verified webhook.
    }
    ```
  </Tab>
</Tabs>

## ステップ 6: アウトバウンド webhook に署名する（セラー）

<Tabs>
  <Tab title="JavaScript/TypeScript">
    `createAdcpServer` に `signerKey` を渡すと、フレームワークがすべてのアウトバウンド webhook に自動署名します:

    ```typescript theme={null}
    serve(() => createAdcpServer({
      name: 'My Seller',
      version: '1.0.0',
      webhooks: {
        signerKey: {
          keyid: 'my-seller-webhook-2026',
          alg: 'ed25519',
          privateKey: webhookPrivateJwk,
        },
      },
      mediaBuy: { /* ... */ },
    }));
    ```
  </Tab>

  <Tab title="Python">
    各アウトバウンド webhook を `sign_webhook` で署名し、送信前に返されたヘッダーを付けます:

    ```python theme={null}
    from adcp.signing import sign_webhook, load_private_key_pem
    import httpx, json

    private_key = load_private_key_pem(open("webhook-private-key.pem", "rb").read())

    async def post_webhook(url: str, payload: dict) -> None:
        body = json.dumps(payload).encode("utf-8")
        headers = {"Content-Type": "application/json"}
        signed = sign_webhook(
            method="POST",
            url=url,
            headers=headers,
            body=body,
            private_key=private_key,
            key_id="my-seller-webhook-2026",
            alg="ed25519",
        )
        headers.update(signed.as_dict())  # adds Signature, Signature-Input, Content-Digest
        async with httpx.AsyncClient() as client:
            await client.post(url, content=body, headers=headers)
    ```
  </Tab>

  <Tab title="Go">
    `ProfileWebhookSigning` で `Signer` を構成し、`SignRequest` またはその `RoundTripper` 経由で使います:

    ```go theme={null}
    priv, _, _ := signing.LoadPrivateKey(webhookPemBytes)
    webhookSigner, _ := signing.NewSigner(signing.SignerOptions{
        KeyID:      "my-seller-webhook-2026",
        Algorithm:  signing.AlgEd25519,
        PrivateKey: priv,
        Profile:    signing.ProfileWebhookSigning,
    })

    webhookClient := &http.Client{
        Transport: webhookSigner.RoundTripper(http.DefaultTransport, true /* always cover content-digest for webhooks */),
    }
    // Use webhookClient.Post / .Do to deliver webhooks; signatures are added automatically.
    ```
  </Tab>
</Tabs>

webhook 署名公開鍵を `"adcp_use": "request-signing"` を持つ JWK として公開してください。webhook 検証者は後方互換性のため非推奨の `"webhook-signing"` 鍵を依然として受理しますが、新しい署名者は `request-signing` を使うべきです。独立した webhook ローテーションまたは爆発半径分離が欲しい場合、webhook 固有の `kid` を持つ別の `request-signing` JWK を公開してください。

## ステップ 7: ケイパビリティを宣言する

セラーがインバウンド署名を検証する場合、バイヤーが署名すべきと分かるよう `get_adcp_capabilities` レスポンスで `signed_requests`（オンワイヤースキーマでのエイリアス `request_signing`）を宣言してください:

<Tabs>
  <Tab title="JavaScript/TypeScript">
    ```typescript theme={null}
    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: { /* ... */ },
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    from adcp.server.responses import capabilities_response

    class MySeller(ADCPHandler):
        async def get_adcp_capabilities(self, params, context=None):
            return capabilities_response(
                ["media_buy"],
                request_signing={
                    "supported": True,
                    "required_for": ["create_media_buy", "update_media_buy"],
                    "supported_for": ["sync_creatives", "sync_audiences"],
                    "covers_content_digest": "either",
                },
            )
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    // In your get_adcp_capabilities handler, set the request_signing block on
    // the response builder:
    return adcp.CapabilitiesResponse(adcp.CapabilitiesData{
        SupportedProtocols: []string{"media-buy"},
        RequestSigning: &adcp.RequestSigningCapability{
            Supported:           true,
            RequiredFor:         []string{"create_media_buy", "update_media_buy"},
            SupportedFor:        []string{"sync_creatives", "sync_audiences"},
            CoversContentDigest: "either",
        },
    }), nil
    ```
  </Tab>
</Tabs>

バイヤーは `get_adcp_capabilities` を呼び、`request_signing.required_for` と `supported_for` を読んで、あなたがどの操作に署名を期待するかを知ります。

## 鍵ローテーション

JWKS エンドポイントはゼロダウンタイムローテーションのため複数の鍵を同時にサポートします:

1. 新しい `kid` を持つ新しいキーペアを生成
2. 新しい公開鍵を JWKS に追加（古いものと新しいもの両方が公開される）
3. 新しい秘密鍵を使うよう署名設定を更新
4. 24〜48 時間後、古い公開鍵を JWKS から削除

緊急ローテーション（鍵侵害）には、古い `kid` を失効リストの `revoked_kids` に追加し、即座に新しい鍵にローテートしてください。失効リスト形式については [Revocation](/docs/building/by-layer/L1/security#revocation) を参照してください。

## Testing

### 適合性ベクター

仕様は `compliance/cache/3.0.0/test-vectors/request-signing/`（ソースは `static/compliance/source/test-vectors/request-signing/`）で **39 個のテストベクター** を出荷します:

* **12 個の正ベクター**: 検証者が受理しなければならない有効な署名付きリクエスト（非 4xx）
* **27 個の負ベクター**: 検証者が `401` と正しいエラーコードで拒否しなければならない無効なリクエスト

```bash theme={null}
# Debug a single vector
adcp signing verify-vector \
  --vector compliance/cache/3.0.0/test-vectors/request-signing/positive/001-basic-post.json
```

### 検証者をグレードする

```bash theme={null}
adcp grade request-signing https://agent.example.com/mcp --auth-token $TOKEN
```

### エラーコード

検証が失敗するとき、`WWW-Authenticate: Signature error="<code>"` を伴う `401` を返します:

| Code                    | Meaning               |
| ----------------------- | --------------------- |
| `missing_signature`     | 必要なときに署名ヘッダーが存在しない    |
| `invalid_signature`     | 署名が公開鍵に対して検証されない      |
| `expired_signature`     | 署名タイムスタンプが古すぎる        |
| `replayed_nonce`        | nonce が既に使われた         |
| `revoked_key`           | 鍵が失効された               |
| `unknown_key`           | 鍵 ID が JWKS に見つからない   |
| `unsupported_algorithm` | アルゴリズムが allowlist にない |

完全なエラーコード分類については [Transport error taxonomy](/docs/building/by-layer/L1/security#transport-error-taxonomy) を参照してください。

## 関連

* [Security: Signed Requests](/docs/building/by-layer/L1/security#署名付きリクエストトランスポート層) — 検証者チェックリスト、正準化ルール、リプレイ dedup サイジングを伴う規範的仕様
* [Push Notifications](/docs/building/by-layer/L3/webhooks) — 署名検証を含む webhook セットアップ
* [エージェントを検証する](/docs/building/verification/validate-your-agent) — 署名適合性を含む完全なコンプライアンス検証
* [エージェントをビルドする](/docs/building/by-layer/L4/build-an-agent) — SDK セットアップとストーリーボード検証
* [RFC 9421](https://www.rfc-editor.org/rfc/rfc9421) — HTTP Message Signatures 仕様
