---
title: Inference hooks インテグレーションを開発する
url: https://platform.claude.com/docs/ja/manage-claude/inference-hooks-endpoint
description: 署名付きのInference hooksリクエストを受信して検証し、許可または拒否の判定を返すAIセキュリティサーバーを構築します。
---

<Note>
  Inference hooksはベータ版で、Claude Enterprise組織で利用できます。ベータ期間中は、フィールド名、リクエストの形式、ヘッダーが変更される可能性があります。
</Note>

Inference hooks統合とは「AI security server」（AIセキュリティサーバー）のことで、Anthropicが呼び出すHTTPSサービスです。管理対象のリクエストごとに、サーバーは会話のトランスクリプトを含む署名付きの`POST`を受信し、許可または拒否の「verdict」（判定）を返します。このページでは、そのサーバーを構築するためのプロトコルとして、リクエストと判定のスキーマ、署名検証、運用上の取り決めを説明します。

Inference hooksを有効にしてエンドポイントを指定する方法については、[Inference hooksを設定する](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-configuration)を参照してください。Inference hooksの概要と使用すべき場面については、[Inference hooksの概要](https://platform.claude.com/docs/ja/manage-claude/inference-hooks)を参照してください。

## 最初の判定のやり取りを確認する

最小限の動作する統合は、各リクエストを読み取って許可するだけのサーバーです。以下のいずれかのサーバーを実行し、公開された`https://` URLで公開します。たとえば、自分で管理するホスト上のTLS終端リバースプロキシの背後に配置します。リバーストンネルサービスは使用しないでください（[リクエストを受信する](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-endpoint#receive-a-request)を参照）。次に、管理者に[エンドポイントとして設定して接続をテスト](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-configuration)してもらいます。**Test connection**の結果に、サーバーが返した許可の判定が表示されます。

<CodeGroup exclude="shell">
  ```python Python
  # 実行方法: python server.py
  from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer


  class VerdictHandler(BaseHTTPRequestHandler):
      protocol_version = "HTTP/1.1"  # keep the connection open between verdicts

      def do_POST(self):
          # ボディを読み切ります。トランスクリプトは数メガバイトになることがあります。
          self.rfile.read(int(self.headers.get("Content-Length", 0)))
          verdict = b'{"action": "allow"}'
          self.send_response(200)
          self.send_header("Content-Type", "application/json")
          self.send_header("Content-Length", str(len(verdict)))
          self.end_headers()
          self.wfile.write(verdict)


  ThreadingHTTPServer(("", 8000), VerdictHandler).serve_forever()
  ```

  ```typescript TypeScript
  // 実行方法: node server.ts
  import { createServer } from "node:http";

  createServer((request, response) => {
    // 応答する前にボディを読み切ります。トランスクリプトは数メガバイトになることがあります。
    request.resume();
    request.on("end", () => {
      response.writeHead(200, { "Content-Type": "application/json" });
      response.end('{"action": "allow"}');
    });
  }).listen(8000);
  ```

  ```csharp C#
  #:sdk Microsoft.NET.Sdk.Web
  #:property PublishAot=false
  // 実行方法: dotnet run server.cs

  var app = WebApplication.Create();

  app.MapPost("/{**path}", async (HttpRequest request) =>
  {
      // ボディを読み切ります。トランスクリプトは数メガバイトになることがあります。
      await request.Body.CopyToAsync(Stream.Null);
      return Results.Text("""{"action": "allow"}""", "application/json");
  });

  app.Run("http://0.0.0.0:8000");
  ```

  ```go Go
  // 実行方法: go run server.go
  package main

  import (
  	"io"
  	"log"
  	"net/http"
  )

  func main() {
  	http.HandleFunc("POST /", func(writer http.ResponseWriter, request *http.Request) {
  		// 接続を再利用できるようボディを読み切ります。トランスクリプトは数メガバイトになることがあります。
  		io.Copy(io.Discard, request.Body)
  		writer.Header().Set("Content-Type", "application/json")
  		writer.Write([]byte(`{"action": "allow"}`))
  	})
  	log.Fatal(http.ListenAndServe(":8000", nil))
  }
  ```

  ```java Java
  // 実行方法: java VerdictServer.java
  import com.sun.net.httpserver.HttpServer;

  void main() throws IOException {
      HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
      server.createContext("/", exchange -> {
          // ボディはバッファリングせずに読み捨てます。トランスクリプトは数メガバイトになる場合があります。
          exchange.getRequestBody().transferTo(OutputStream.nullOutputStream());
          byte[] verdict = "{\"action\": \"allow\"}".getBytes(StandardCharsets.UTF_8);
          exchange.getResponseHeaders().set("Content-Type", "application/json");
          exchange.sendResponseHeaders(200, verdict.length);
          try (OutputStream responseBody = exchange.getResponseBody()) {
              responseBody.write(verdict);
          }
      });
      server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
      server.start();
  }
  ```

  ```php PHP
  <?php
  // 実行方法: php -S 0.0.0.0:8000 server.php

  // ボディを読み切ります。トランスクリプトは数メガバイトになることがあります。
  file_get_contents('php://input');

  http_response_code(200);
  header('Content-Type: application/json');
  echo '{"action": "allow"}';
  ```

  ```ruby Ruby
  # Ruby 3.4 では webrick は通常の gem です。gem install webrick を実行するか、gem "webrick" を追加してください。
  # 実行方法: ruby server.rb
  require "webrick"

  server = WEBrick::HTTPServer.new(Port: 8000)
  server.mount_proc("/") do |request, response|
    request.body # Drain the body; transcripts can be megabytes.
    response.status = 200
    response["Content-Type"] = "application/json"
    response.body = '{"action": "allow"}'
  end
  server.start
  ```
</CodeGroup>

<Note>
  これらのサーバーは、署名のないリクエストも含めてすべてのリクエストを受け入れます。適用を開始する前に、[署名検証](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-endpoint#verify-the-signature)を追加してください。
</Note>

## リクエストを受信する

Anthropicは、管理者が設定したURLにHTTPS `POST`を送信します。設定されたURL全体がエンドポイントです。固定のパスサフィックスはないため、サーバーに適した任意のパスを選択してください。

AIセキュリティサーバーは、Anthropicから到達できる場所でホストしてください。つまり、ポート443の`https://` URLで、パブリックにルーティング可能なホスト上にあり（プライベート、ループバック、キャリアグレードNATの範囲は接続時に拒否されます）、パブリックCAトラストストアに対して検証可能な証明書を持ち、リダイレクトなしで応答する必要があります。ホストにはIPv4アドレスが必要で、ホストがIPv6アドレスも持っている場合でもAnthropicはIPv4アドレスを使用します。ホストが`localhost`またはIPv6アドレスであるURLは拒否されます。設定されたURLは最終的な宛先である必要があります。リバーストンネルのホスト（ngrokや同様のトンネルサービス）はサポートされていません。Anthropicのネットワークポリシーによってブロックされます。サーバーは自分で管理するドメインでホストしてください。管理者がURLを設定してテストする方法については、[Inference hooksを設定する](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-configuration)で説明しています。

すべてのリクエストには、以下の固定ヘッダーに加えて、管理者が設定した[カスタムリクエストヘッダー](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-configuration)と、組織に署名シークレットがある場合は[署名を検証する](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-endpoint#verify-the-signature)で説明する`webhook-*`署名ヘッダーが含まれます。

| ヘッダー              | 値                  |
| ----------------- | ------------------ |
| `Content-Type`    | `application/json` |
| `User-Agent`      | `anthropic-dlp/1`  |
| `Accept-Encoding` | `identity`         |

フックイベントは2種類あり、トップレベルの`type`フィールドで区別されます。プロンプトフレームは、管理対象の推論リクエストごとに1回、推論が始まる前に送信されます。ツール呼び出しフレームは、**Validate tool calls**が有効になっている組織で、モデルの応答にツール呼び出しが含まれる場合に、そのいずれかが実行される前に送信されます。いずれの場合も、AnthropicはAIセキュリティサーバーが応答するか、判定のタイムアウトが経過するまで待機します。

## プロンプトフレーム

リクエストボディは、以下のフィールドを持つJSONオブジェクトです。

| フィールド        | 型               | 説明                                                                                                                                                                                                                                                                                                              |
| ------------ | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`       | string          | フックイベント：`"prompt"`または`"tool_call"`（[ツール呼び出しフレーム](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-endpoint#the-tool-call-frame)を参照）。今後、他のイベントタイプが導入される予定のため、認識できない値は適切に処理してください（[前方互換性](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-endpoint#forward-compatibility)を参照）。 |
| `request_id` | string          | 相関付けのための、フレームごとの不透明な識別子。`webhook-id`ヘッダーと同じ値です。                                                                                                                                                                                                                                                                 |
| `tenant_id`  | string または null | リクエストが属する組織の不透明な識別子。                                                                                                                                                                                                                                                                                            |
| `actor`      | object          | リクエストの帰属先となるプリンシパルで、`type`で区別されます（現在送信される値は`"user"`のみ）：`id`（同じアカウントのリクエスト間で安定したタグ付き識別子）と`email_address`（利用可能な場合）。`id`と`email_address`はどちらもnullになる可能性があります。                                                                                                                                                      |
| `source`     | object          | 発信元のアプリケーション：`application`（[ソースの値](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-endpoint#source-values)を参照）。                                                                                                                                                                               |
| `messages`   | array           | 推論時点までの会話のトランスクリプト。[コンテンツブロック](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-endpoint#content-blocks)を参照してください。                                                                                                                                                                             |
| `session_id` | string または null | 会話の不透明な識別子（存在する場合）。解析しないでください。Claude Codeの場合、これはベストエフォートの、クライアントが主張するセッション識別子です。                                                                                                                                                                                                                                |
| `model`      | string または null | このリクエストの公開モデル識別子（利用可能な場合）。                                                                                                                                                                                                                                                                                      |
| `metadata`   | object          | 文字列キーから文字列値への予約済み拡張マップで、現在は空で送信されます。ここから何も必須としないでください。また、存在しない場合、存在する場合、どのようなキーが現れる場合にも対応できるようにしてください。                                                                                                                                                                                                          |

リクエストボディの例：

```json
{
  "type": "prompt",
  "request_id": "req_abc123",
  "tenant_id": "11111111-1111-1111-1111-111111111111",
  "actor": {
    "type": "user",
    "id": "user_01AbCdEfGhIjKlMnOpQrStUv",
    "email_address": "alice@example.com"
  },
  "source": {
    "application": "claude-ai"
  },
  "session_id": "22222222-2222-2222-2222-222222222222",
  "model": "claude-sonnet-4-5",
  "messages": [
    {
      "role": "user",
      "content": [
        {
          "type": "text",
          "text": "Summarize the attached report."
        },
        {
          "type": "attachment",
          "file_name": "q2-report.pdf",
          "media_type": "application/pdf",
          "size_bytes": 48213,
          "text": "Q2 revenue grew 14% quarter over quarter..."
        }
      ]
    }
  ],
  "metadata": {}
}
```

### コンテンツブロック

`messages`の各エントリには、`user`または`assistant`の`role`（ツールの結果は、公開Messages APIのコンテンツモデルに合わせて`user`ロールの下に表示されます）と、`type`で区別されるブロックの`content`配列があります。

| ブロックの`type`   | フィールド                                                                                                                                                                                                                                                                         |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `text`        | `text`：テキストコンテンツ。                                                                                                                                                                                                                                                             |
| `tool_use`    | `id`：対応するツール結果が参照する識別子。`tool_name`：ツールの名前。`input`：モデルがツールに渡した引数。 `tool_info`：ツール呼び出しフレームにのみ含まれ（それ以外では省略され、`null`になることはありません）、ツールを実行または提供する主体を示すオブジェクト。[ツール呼び出しフレーム](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-endpoint#the-tool-call-frame)を参照してください。 |
| `tool_result` | `content`：ツールの出力をテキストとしたもので、各部分は改行で結合されます。画像などのバイナリ部分はプレースホルダーマーカーに置き換えられ、生のバイトが送信されることはありません。`is_error`：ツール呼び出しが失敗したかどうか。`tool_name`：ツールの名前。これにより、ポリシーは以前のブロックを相互参照することなくツールの識別情報に基づいて条件を設定できます。`tool_use_id`：対応する`tool_use`ブロックの`id`。                                       |
| `attachment`  | `file_name`：元のファイル名またはパス。`media_type`：添付ファイルのメディアタイプ。`size_bytes`：元のファイルのサイズ。`text`：利用可能な場合の添付ファイルのテキストコンテンツ（抽出されたドキュメントのテキスト、音声の文字起こし、リンクのメタデータなど）。添付ファイルの生のバイトが送信されることはありません。                                                                                               |

`type`、`text`ブロックの`text`、`tool_result`ブロックの`content`と`is_error`を除き、これらのフィールドはいずれも値が不明な場合に`null`になる可能性があります。たとえば、画像は`file_name`と`text`が`null`に設定された`attachment`ブロックとして届きます。

認識できない`type`を持つブロックは、前方互換性のための追加です。保証されるフィールドは`type`のみです。ポリシーは存在する他のフィールドを検査してもかまいませんが、認識できないタイプを理由にリクエストを拒否してはなりません。

### トランスクリプトの内容

トランスクリプトは、推論時点までの会話をエンドユーザーが見ているとおりに表したものです。トランスクリプトのテキスト、ツール呼び出しとその結果、添付ファイルから抽出されたテキスト、以前のターンが含まれます。システムプロンプト、ツール定義、Anthropic内部のコンテキスト、Claudeの非表示の推論、生のファイルバイトは含まれません。

すべてのブロックが除外されたターンは丸ごと省略されます。そのため、userとassistantが厳密に交互に並ぶことを前提にしないでください。

トランスクリプトは切り詰められずに送信されます。そのため、大きな添付ファイルを含む長い会話では、リクエストボディも大きくなります。実際には、モデルのコンテキストウィンドウによってボディは約10 MB未満に収まりますが、プロトコル上は最大64 MiBまで許容されます。一般的なデフォルト値の中には、これよりはるかに小さいものがあります。たとえば、nginxの`client_max_body_size`は1 MB、Expressの`express.json()`は100 kBです。ボディが拒否されるとWebhook障害として扱われます。そのため、障害時の処理が**Allow the request**の場合、サイズの大きすぎるプロンプトは検査されないままモデルに届きます。

### ソースの値

`source.application`は閉じた列挙型ではなく、オープンな文字列です。一般的な値は`claude-ai`、`claude-code`、`cowork`です。[接続テスト](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-configuration)と自動的なサーキットブレーカーの[復旧チェック](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-endpoint#circuit-breaker)では`config-test`が使用されます。新しい値が現れる可能性があり、サーバーは認識できない値を理由にリクエストを拒否してはなりません。

`source.application`は信頼境界ではなく、参考程度のルーティングメタデータとして扱ってください。セキュリティ上重要なポリシー判断をこれだけに依存させないでください。

## ツール呼び出しフレーム

Claudeがツール呼び出しを生成すると、Anthropicはそれらを列挙した1つのツール呼び出しフレームを送信します。claude.ai独自のツールの一部への呼び出しは省略される場合があり、ツール呼び出しがすべて省略されたものだけである応答では、ツール呼び出しフレームは生成されません。[利用可能範囲](https://platform.claude.com/docs/ja/manage-claude/inference-hooks#availability)を参照してください。1つの判定がフレーム全体に適用されます。一部のツール呼び出しを許可し、他を拒否することはできません。このフレームは、プロンプトフレームと同じエンドポイントに、同じヘッダー、署名、トップレベルフィールドで送信されます。Claudeがコード実行ツールで実行するコードによって行われるツール呼び出しも、同じ方法で、別のツール呼び出しフレームとして、実行前に送信されます。

プロンプトフレームとは次の3点が異なります。

* `type`は`"tool_call"`です。
* `messages`には最新のメッセージ、つまりClaudeが生成したばかりの`assistant`メッセージのみが含まれます。これには任意の`text`ブロックと、フレームが列挙するツール呼び出しごとに1つの`tool_use`ブロックが、モデルが生成した順序で含まれます。それ以前の会話は、そのモデル呼び出しの前に送信されたプロンプトフレームに含まれていたため省略されます。プロトコルが今後その前に以前のメッセージを追加する可能性があるため、`messages`の最後のエントリを読み取ってください。
* 各`tool_use`ブロックには、ツールを実行または提供する主体を示す`tool_info`オブジェクトが含まれます。

`session_id`が設定されている場合、両方のフレームで同じ値になります。ツール呼び出しフレームには独自の`request_id`があり、プロンプトフレームのものと同様に不透明です。

`tool_info`は、ツールが何にアクセスできるかではなく、ツールを実行または提供する主体を示します。これは`type`フィールドで区別される4種類のいずれかであり、各種類には独自のフィールドがあります。Anthropicは、以下の種類のうちツールに当てはまる最初のものを送信します。新しい種類が現れる可能性があります。認識できない`type`は受け入れ、そのような種類については`type`のみに依存してください。

該当しないオプションのフィールドは省略され、`null`になることはないため、`tool_info`が単に`{"type": "client"}`である場合もあります。`tool_name`はツールを定義した者が選択し、サーバーの`toolset_name`はリクエストを作成した者が選択するため、どちらも信頼境界として扱わないでください。

### プラットフォームツール

プラットフォームツールとは、Web検索やコード実行など、Claude API自体がリクエストを処理する際に実行するツールです。

| フィールド          | 存在    | 説明                                                                      |
| -------------- | ----- | ----------------------------------------------------------------------- |
| `type`         | 常に    | `"platform"`                                                            |
| `tool_type`    | 常に    | `web_search_20250305`などの、ツールのバージョン付きタイプ。完全一致で照合し、名前や日付を解析して取り出さないでください。 |
| `toolset_name` | オプション | ツールが属するツールのグループ。                                                        |

### アプリケーションツール

アプリケーションツールとは、claude.ai独自のツールなど、リクエストを行うAnthropicアプリケーション自体が提供するツールです。

| フィールド          | 存在    | 説明               |
| -------------- | ----- | ---------------- |
| `type`         | 常に    | `"application"`  |
| `toolset_name` | オプション | ツールが属するツールのグループ。 |

claude.ai自体が提供するWeb検索ツールに対する`tool_use`ブロック：

```json
{
  "type": "tool_use",
  "id": "toolu_01LmNoPqRsTuVwXyZaBcDeFg",
  "tool_name": "web_search",
  "input": {
    "query": "comparable deals for unannounced 4.2B USD Acme Corp acquisition"
  },
  "tool_info": {
    "type": "application"
  }
}
```

claude.aiはこのツールを常に`web_search`と名付けるわけではないため、名前だけで照合しないでください。`input`には常に検索クエリが含まれ、他のフィールドが含まれる場合もあります。

### サードパーティツール

サードパーティツールとは、claude.aiのコネクタや、リクエストが`mcp_servers`で指定するMCPサーバーなど、Anthropicが把握しているサーバー上のツールです。これはAnthropicがそのサーバーを審査したことを意味するものではありません。

| フィールド             | 存在               | 説明                                                                                                                     |
| ----------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `type`            | 常に               | `"third_party"`                                                                                                        |
| `toolset_name`    | オプション            | リクエストがサーバーに付けた名前。                                                                                                      |
| `origin`          | オプション            | サーバーのURLのスキーム、ホスト、およびデフォルト以外のポート。`localhost`を含む、プライベートまたはローカルのアドレスである場合があります。`origin`がない場合は、安全であることではなく、不明であることを意味します。 |
| `verified_origin` | `origin`がある場合は常に | Anthropicのサーバー自体がそのサーバーに接続する場合にのみ`true`になります。`false`は、Anthropicが確認していないオリジンとして扱ってください。                                 |

リクエストが`mcp_servers`で`crm`と名付けたMCPサーバー上のツールに対する`tool_use`ブロック：

```json
{
  "type": "tool_use",
  "id": "toolu_01GhIjKlMnOpQrStUvWxYzAb",
  "tool_name": "crm_search",
  "input": {
    "query": "accounts renewing in Q4"
  },
  "tool_info": {
    "type": "third_party",
    "toolset_name": "crm",
    "origin": "https://mcp.crm.example.com",
    "verified_origin": true
  }
}
```

### クライアントツール

クライアントツールとは、それ以外のすべてのツールで、通常はClaudeを呼び出すアプリケーションが実行するツールです。bashやコンピューター使用など、Anthropicが定義し、呼び出し元のアプリケーションが実行するツールもクライアントツールであり、`tool_type`にバージョン付きタイプが含まれます。リクエストで宣言されていないツールへの呼び出しも`"client"`になります。

ローカルMCPサーバーなど、Claude Codeがユーザーのコンピューターから直接接続するMCPサーバー上のツールは、クライアントツールです。その`tool_info`は`{"type": "client"}`であり、`tool_name`はClaude Codeが付けた名前で、`mcp__<server>__<tool>`の形式になります。

拒否すると、Claude Codeが実行する前に呼び出しが停止されます。Claude Codeとローカルサーバー間のやり取りはAnthropicを経由しないため、AIセキュリティサーバーがツールの結果を確認できるのは、Claude Codeがそれを送り返した場合のみです。その場合、そのテキストは次のプロンプトフレームの`tool_result`ブロックに含まれます。

| フィールド          | 存在    | 説明                                                                                       |
| -------------- | ----- | ---------------------------------------------------------------------------------------- |
| `type`         | 常に    | `"client"`                                                                               |
| `tool_type`    | オプション | Anthropicがツールを定義している場合の、`bash_20250124`などのツールのバージョン付きタイプ。完全一致で照合し、名前や日付を解析して取り出さないでください。 |
| `toolset_name` | オプション | `browser`など、ツールが属するツールのグループ。                                                             |

呼び出し元のアプリケーションが宣言するツールに対する`tool_use`ブロック：

```json
{
  "type": "tool_use",
  "id": "toolu_01AbCdEfGhIjKlMnOpQrStUv",
  "tool_name": "read_file",
  "input": {
    "path": "reports/q3.txt"
  },
  "tool_info": {
    "type": "client"
  }
}
```

Anthropicが定義し、呼び出し元のアプリケーションが実行するbashに対する`tool_use`ブロック：

```json
{
  "type": "tool_use",
  "id": "toolu_01HiJkLmNoPqRsTuVwXyZaBc",
  "tool_name": "bash",
  "input": {
    "command": "ls -la reports/"
  },
  "tool_info": {
    "type": "client",
    "tool_type": "bash_20250124"
  }
}
```

## 判定を返す

どちらの結果の場合も、HTTP 200とJSONの判定ボディで応答してください。`action`フィールドで区別されます。リクエストを許可するには：

```json
{
  "action": "allow"
}
```

拒否するには：

```json
{
  "action": "deny",
  "deny_reason": "This prompt appears to contain customer payment card data, which your organization's policy does not allow.",
  "reference_id": "scan_01HXPT4R9V"
}
```

| フィールド          | 制約                                           | セマンティクス                                                                                                                                                                                                            |
| -------------- | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `action`       | `"allow"`または`"deny"`。必須                      | `allow`は推論の続行を許可し、`deny`は推論を拒否します。                                                                                                                                                                                 |
| `deny_reason`  | string または null。最大500文字で、それより長い値は切り詰められます    | `action`が`deny`の場合にエンドユーザーに表示されます。`allow`の場合は無視されます。                                                                                                                                                               |
| `reference_id` | string または null。`[A-Za-z0-9._:/-]`の文字で最大50文字 | この評価に対する独自の識別子。拒否の`inference_hooks_request_denied` [コンプライアンスアクティビティ](https://platform.claude.com/docs/ja/manage-claude/compliance-activity-feed)に記録され、エンドユーザーに表示されることはありません。不透明な値にしてください。リクエストの内容や個人データを含めないでください。 |

書式の問題によって拒否が破棄されることはありません。サイズ超過の`deny_reason`は切り詰められ、不正な形式の`reference_id`は通知なしに破棄され、`action`は引き続き尊重されます。

逆は成り立ちません。解析可能な判定を伴うHTTP 200以外のものはすべてWebhook障害であり、判定の代わりに組織の[障害時の処理](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-configuration)が適用されます。特に次の点に注意してください。

* エラーステータスで拒否を示さないでください。200以外の応答は拒否ではなく失敗です。
* `allow`または`deny`以外の`action`値は、Webhook障害として扱われます。

Anthropicは応答ボディを最大64 KiBまで読み取り、ボディは非圧縮である必要があります。リダイレクトは追跡されず、Cookieは無視されます。判定ボディ内の不明なフィールドは無視されるため、ここで説明するフィールドに加えて、より豊富なオブジェクトを返すことができます。

ツール呼び出しフレームも同じ判定ボディを受け取り、このセクションのすべてのルールがそのまま適用されます。`allow`はツール呼び出しの実行を許可して応答を続行させ、`deny`はそれらすべてを停止し、`deny_reason`を含め、拒否されたプロンプトと同じエラーで応答を終了します。最初のツール呼び出しの前に配信されたテキストは取り消されません。

## 署名を検証する

リクエストは、[Standard Webhooks](https://www.standardwebhooks.com/)仕様に従い、3つのヘッダーを使って署名されます。Anthropicはヘッダー名を小文字で送信しますが、プロキシによって大文字・小文字が変更される場合があります。そのため、ヘッダーは大文字と小文字を区別せずに参照してください。

| ヘッダー                | 内容                                                                                                                                               |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `webhook-id`        | この配信の一意の識別子。ボディの`request_id`と同じ値です。冪等性キーとして、また署名対象ペイロードの最初の要素として使用します。                                                                           |
| `webhook-timestamp` | リクエストが署名された時刻。10進数文字列で表したUnix時間（秒）です。サーバーの時計と前後どちらかに5分以上ずれているタイムスタンプは拒否してください。                                                                   |
| `webhook-signature` | スペース区切りの1つ以上の`v1,<base64>`値。各値は`{webhook-id}.{webhook-timestamp}.{raw body bytes}`に対するHMAC-SHA256です。定数時間比較を使い、いずれかの値が自分で計算した値と一致すればリクエストを受け入れます。 |

検証のバグの大半は、次の2つの点が原因です。

* **生のバイトを検証する。** JSONの解析や再エンコードを行う前に、受信したとおりのボディに対してHMACを計算してください。
* **シークレットは標準のbase64デコーダーでデコードする。** 署名シークレットは`whsec_`プレフィックスの後ろの値で、ヘッダー内の署名と同じく標準のbase64アルファベット（`+`と`/`）でエンコードされています。URLセーフなデコーダーを使うと、シークレットに`+`や`/`が含まれる場合に誤ったキーバイトが導出されます。そして、ほとんどのシークレットにはこれらの文字が含まれます。

組織に署名シークレットがある場合、Anthropicが送信するリクエストはすべて署名されます。接続テストも例外ではありません。セットアップフローでは、最初のテストの前にシークレットが生成されるためです。[Inference hooksを有効にするにはシークレットが必要](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-configuration)なので、署名のないリクエストはすべて拒否してください。

ただし、例外が1つあります。シークレットが必須になる前にInference hooksを有効にした組織では、管理者がシークレットを生成するまで署名のないリクエストが送信され続けます。署名のないリクエストは、シークレットが存在することを管理者が確認するまでの間だけ受け入れ、その後は拒否してください。

[シークレットのローテーション](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-configuration#rotate-your-signing-secret)は即座に切り替わります。ただし、以前のシークレットで署名されたリクエストが、その後約1分間届く可能性があります。すでに送信中のリクエストも同様です。これらの遅れて届くリクエストが拒否されないように、切り替え期間中はAIセキュリティサーバーで両方のシークレットによる署名を受け入れてください。

以下のサンプルはサーバー実装です。AIセキュリティサーバーは単発のリクエストではなく長時間稼働するHTTPSサービスであるため、shellタブはありません。各サンプルは、その言語の標準ライブラリのみを使用しています。[Standard Webhooks](https://www.standardwebhooks.com/)プロジェクトでは、ほとんどの言語向けに検証ライブラリも公開されています。

<CodeGroup exclude="shell">
  ```python Python
  import base64
  import hashlib
  import hmac
  import time

  TOLERANCE_SECONDS = 300


  def verify(secret: str, headers: dict[str, str], body: bytes) -> bool:
      """Return True if the body was signed by Anthropic for this organization.

      Anthropic sends header names in lowercase, but proxies are free to
      re-case them, so normalize the lookup to lowercase.
      """
      lowercased = {name.lower(): value for name, value in headers.items()}
      try:
          message_id = lowercased["webhook-id"]
          timestamp = lowercased["webhook-timestamp"]
          signatures = lowercased["webhook-signature"]
      except KeyError:
          return False  # unsigned request: not from Anthropic

      try:
          signed_at = int(timestamp)
      except ValueError:
          return False
      if abs(time.time() - signed_at) > TOLERANCE_SECONDS:
          return False  # replayed, or the clocks disagree

      try:
          key = base64.b64decode(secret.removeprefix("whsec_"), validate=True)
      except ValueError:
          return False  # misconfigured secret: reject rather than crash

      payload = f"{message_id}.{timestamp}.".encode() + body
      expected = b"v1," + base64.b64encode(
          hmac.new(key, payload, hashlib.sha256).digest()
      )

      # バイト列で比較します。str に対する compare_digest は非 ASCII 入力で例外を送出します。
      return any(
          hmac.compare_digest(expected, candidate.encode())
          for candidate in signatures.split()
      )
  ```

  ```typescript TypeScript
  import { createHmac, timingSafeEqual } from "node:crypto";
  import type { IncomingHttpHeaders } from "node:http";

  const TOLERANCE_SECONDS = 300;

  /**
   * Returns true if the body was signed by Anthropic for this organization.
   *
   * Node lowercases incoming header names, matching how Anthropic sends
   * them, so look them up in lowercase.
   */
  export function verify(secret: string, headers: IncomingHttpHeaders, body: Buffer): boolean {
    const messageId = headers["webhook-id"];
    const timestamp = headers["webhook-timestamp"];
    const signatures = headers["webhook-signature"];
    if (
      typeof messageId !== "string" ||
      typeof timestamp !== "string" ||
      typeof signatures !== "string"
    ) {
      return false; // unsigned request: not from Anthropic
    }

    const signedAt = Number(timestamp);
    if (
      !Number.isFinite(signedAt) ||
      Math.abs(Date.now() / 1000 - signedAt) > TOLERANCE_SECONDS
    ) {
      return false; // replayed, or the clocks disagree
    }

    const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
    const payload = Buffer.concat([Buffer.from(`${messageId}.${timestamp}.`), body]);
    const expected = Buffer.from(
      "v1," + createHmac("sha256", key).update(payload).digest("base64")
    );

    return signatures.split(" ").some((candidate) => {
      const candidateBytes = Buffer.from(candidate);
      return (
        candidateBytes.length === expected.length && timingSafeEqual(candidateBytes, expected)
      );
    });
  }
  ```

  ```csharp C#
  using System.Security.Cryptography;
  using System.Text;

  static class InferenceHooks
  {
      private const int ToleranceSeconds = 300;

      /// <summary>
      /// 本文がこの組織向けに Anthropic によって署名されている場合に true を返します。
      /// Anthropic はヘッダー名を小文字で送信しますが、プロキシが大文字小文字を
      /// 変更する可能性があるため、大文字小文字を区別せずに照合します。
      /// </summary>
      public static bool Verify(string secret, IReadOnlyDictionary<string, string> headers, byte[] body)
      {
          // プロキシが大文字小文字違いの重複名を送った場合、TryAdd は最初の値を保持します。
          // コピーコンストラクターの場合は代わりに例外をスローします。
          var lookup = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
          foreach (var (name, value) in headers)
          {
              lookup.TryAdd(name, value);
          }

          if (!lookup.TryGetValue("webhook-id", out var messageId) ||
              !lookup.TryGetValue("webhook-timestamp", out var timestamp) ||
              !lookup.TryGetValue("webhook-signature", out var signatures))
          {
              return false; // unsigned request: not from Anthropic
          }

          if (!long.TryParse(timestamp, out var signedAt) ||
              Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - signedAt) > ToleranceSeconds)
          {
              return false; // replayed, or the clocks disagree
          }

          // 標準の base64 アルファベットを使用します。URL セーフなデコーダーでは誤ったキーバイトが得られます。
          var encodedKey = secret.StartsWith("whsec_") ? secret["whsec_".Length..] : secret;
          byte[] key;
          try
          {
              key = Convert.FromBase64String(encodedKey);
          }
          catch (FormatException)
          {
              return false; // misconfigured secret: reject rather than crash
          }

          byte[] payload = [.. Encoding.UTF8.GetBytes($"{messageId}.{timestamp}."), .. body];
          var expected = Encoding.UTF8.GetBytes(
              "v1," + Convert.ToBase64String(HMACSHA256.HashData(key, payload)));

          // FixedTimeEquals は定数時間で動作し、長さが一致しない場合は false を返します。
          return signatures.Split(' ', StringSplitOptions.RemoveEmptyEntries).Any(candidate =>
              CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(candidate), expected));
      }
  }
  ```

  ```go Go
  package hooks

  import (
  	"crypto/hmac"
  	"crypto/sha256"
  	"encoding/base64"
  	"net/http"
  	"strconv"
  	"strings"
  	"time"
  )

  const toleranceSeconds = 300

  // verify は、body がこの組織向けに Anthropic によって署名されたかどうかを報告します。
  // net/http は検索時にヘッダー名を正規化するため、大文字小文字が異なる名前でも一致します。
  func verify(secret string, header http.Header, body []byte) bool {
  	messageID := header.Get("webhook-id")
  	timestamp := header.Get("webhook-timestamp")
  	signatures := header.Get("webhook-signature")
  	if messageID == "" || timestamp == "" || signatures == "" {
  		return false // unsigned request: not from Anthropic
  	}

  	signedAt, err := strconv.ParseInt(timestamp, 10, 64)
  	if err != nil {
  		return false
  	}
  	age := time.Now().Unix() - signedAt
  	if age > toleranceSeconds || age < -toleranceSeconds {
  		return false // replayed, or the clocks disagree
  	}

  	// 標準の base64 アルファベットを使用します。URL セーフなデコーダーでは誤ったキーバイトが導出されます。
  	key, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(secret, "whsec_"))
  	if err != nil {
  		return false
  	}

  	mac := hmac.New(sha256.New, key)
  	mac.Write([]byte(messageID + "." + timestamp + "."))
  	mac.Write(body)
  	expected := "v1," + base64.StdEncoding.EncodeToString(mac.Sum(nil))

  	for _, candidate := range strings.Fields(signatures) {
  		if hmac.Equal([]byte(candidate), []byte(expected)) { // constant-time
  			return true
  		}
  	}
  	return false
  }
  ```

  ```java Java
  import java.nio.charset.StandardCharsets;
  import java.security.GeneralSecurityException;
  import java.security.MessageDigest;
  import java.time.Instant;
  import java.util.Base64;
  import java.util.HashMap;
  import java.util.Locale;
  import java.util.Map;
  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;

  public final class InferenceHookVerifier {
      private static final long TOLERANCE_SECONDS = 300;

      /**
       * Returns true if the body was signed by Anthropic for this organization.
       *
       * <p>Anthropic sends header names in lowercase, but proxies are free to
       * re-case them, so normalize the lookup to lowercase.
       */
      public static boolean verify(String secret, Map<String, String> headers, byte[] body) {
          Map<String, String> lowercased = new HashMap<>();
          headers.forEach((name, value) -> lowercased.put(name.toLowerCase(Locale.ROOT), value));

          String messageId = lowercased.get("webhook-id");
          String timestamp = lowercased.get("webhook-timestamp");
          String signatures = lowercased.get("webhook-signature");
          if (messageId == null || timestamp == null || signatures == null) {
              return false; // unsigned request: not from Anthropic
          }

          long signedAt;
          try {
              signedAt = Long.parseLong(timestamp);
          } catch (NumberFormatException _) {
              return false;
          }
          if (Math.abs(Instant.now().getEpochSecond() - signedAt) > TOLERANCE_SECONDS) {
              return false; // replayed, or the clocks disagree
          }

          // 標準の base64 アルファベットです。URL セーフなデコーダーでは誤ったキーバイトが導出されます。
          byte[] key;
          try {
              key = Base64.getDecoder().decode(
                      secret.startsWith("whsec_") ? secret.substring("whsec_".length()) : secret);
          } catch (IllegalArgumentException _) {
              return false; // misconfigured secret: reject rather than crash
          }

          byte[] expected;
          try {
              Mac mac = Mac.getInstance("HmacSHA256");
              mac.init(new SecretKeySpec(key, "HmacSHA256"));
              mac.update((messageId + "." + timestamp + ".").getBytes(StandardCharsets.UTF_8));
              expected = ("v1," + Base64.getEncoder().encodeToString(mac.doFinal(body)))
                      .getBytes(StandardCharsets.UTF_8);
          } catch (GeneralSecurityException impossible) {
              // すべての JVM に HmacSHA256 が同梱されているため、実行時にこれが発生することはありません。
              throw new IllegalStateException(impossible);
          }

          for (String candidate : signatures.split(" ")) {
              if (MessageDigest.isEqual(candidate.getBytes(StandardCharsets.UTF_8), expected)) {
                  return true; // MessageDigest.isEqual is constant-time
              }
          }
          return false;
      }
  }
  ```

  ```php PHP
  const TOLERANCE_SECONDS = 300;

  /**
   * Returns true if the body was signed by Anthropic for this organization.
   *
   * Anthropic sends header names in lowercase, but proxies are free to
   * re-case them, so normalize the lookup to lowercase.
   */
  function verify(string $secret, array $headers, string $body): bool
  {
      $lowercased = array_change_key_case($headers, CASE_LOWER);
      $messageId = $lowercased['webhook-id'] ?? null;
      $timestamp = $lowercased['webhook-timestamp'] ?? null;
      $signatures = $lowercased['webhook-signature'] ?? null;
      if ($messageId === null || $timestamp === null || $signatures === null) {
          return false; // unsigned request: not from Anthropic
      }

      $signedAt = filter_var($timestamp, FILTER_VALIDATE_INT);
      if ($signedAt === false || abs(time() - $signedAt) > TOLERANCE_SECONDS) {
          return false; // replayed, or the clocks disagree
      }

      // 標準の base64 アルファベットです。URL セーフなデコーダーでは誤ったキーバイトが導出されます。
      $encodedKey = str_starts_with($secret, 'whsec_') ? substr($secret, strlen('whsec_')) : $secret;
      $key = base64_decode($encodedKey, strict: true);
      if ($key === false) {
          return false;
      }

      $payload = "{$messageId}.{$timestamp}." . $body;
      $expected = 'v1,' . base64_encode(hash_hmac('sha256', $payload, $key, binary: true));

      foreach (explode(' ', $signatures) as $candidate) {
          if (hash_equals($expected, $candidate)) { // constant-time
              return true;
          }
      }
      return false;
  }
  ```

  ```ruby Ruby
  # Ruby 3.4 では base64 は bundled gem です。Bundler 管理のアプリでは gem "base64" を追加してください。
  require "base64"
  require "openssl"

  TOLERANCE_SECONDS = 300

  # 本文がこの組織向けに Anthropic によって署名されていれば true を返します。
  #
  # Anthropic はヘッダー名を小文字で送信しますが、プロキシが大文字小文字を
  # 変更する可能性があるため、検索時は小文字に正規化します。
  def verify(secret, headers, body)
    lowercased = headers.transform_keys(&:downcase)
    message_id = lowercased["webhook-id"]
    timestamp = lowercased["webhook-timestamp"]
    signatures = lowercased["webhook-signature"]
    if message_id.nil? || timestamp.nil? || signatures.nil?
      return false # unsigned request: not from Anthropic
    end

    signed_at = Integer(timestamp, exception: false)
    if signed_at.nil? || (Time.now.to_i - signed_at).abs > TOLERANCE_SECONDS
      return false # replayed, or the clocks disagree
    end

    # 標準の base64 アルファベットを使用します。URL セーフなデコーダーでは誤ったキーバイトが得られます。
    begin
      key = Base64.strict_decode64(secret.delete_prefix("whsec_"))
    rescue ArgumentError
      return false # misconfigured secret: reject rather than crash
    end

    # 本文は別途渡すため、そのエンコーディングをプレフィックスと一致させる必要はありません。
    hmac = OpenSSL::HMAC.new(key, "SHA256")
    hmac.update("#{message_id}.#{timestamp}.")
    hmac.update(body)
    expected = "v1," + Base64.strict_encode64(hmac.digest)

    signatures.split(" ").any? do |candidate|
      # fixed_length_secure_compare は長さが一致しないと例外を送出するため、先に長さを確認します。
      candidate.bytesize == expected.bytesize &&
        OpenSSL.fixed_length_secure_compare(candidate, expected)
    end
  end
  ```
</CodeGroup>

## 運用上の動作

### タイムアウトとリトライ

管理者は、判定のタイムアウトを1～10,000msの範囲で設定します（デフォルトは5,000ms）。この時間枠は、接続、TLSハンドシェイク、リクエスト、応答を含むやり取り全体に適用されます。同じタイムアウトがツール呼び出しフレームにも適用されます。

Anthropicは、接続の試行が失敗した場合にのみ、100msの遅延の後に1回だけリトライします。リトライは同じタイムアウトの時間枠を共有し、同じ`webhook-id`と同じ署名を持ちます。AIセキュリティサーバーが応答した後は、やり取りがリトライされることはありません。

### Webhook障害

次のものはすべてWebhook障害として扱われます。

* タイムアウト
* 200以外のステータス（リダイレクトを含む）
* 解析できない、またはサイズが大きすぎるレスポンスボディ
* 到達できないエンドポイント

Webhook障害が拒否として扱われることはありません。代わりに、組織の[障害時の処理](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-configuration)の設定によって、影響を受けたリクエストをブロックするか、検査なしで続行するかが決まります。

### サーキットブレーカー

AIセキュリティサーバーに起因するWebhook障害が継続すると、「circuit breaker」（サーキットブレーカー）が作動して適用が停止されます。Anthropicはサーバーへの接続を停止し、すべてのリクエストに障害時の処理が適用されます。

作動から10分後以降、Anthropicはサーバーが復旧したかどうかを確認します。最大で約1分に1回、**Test connection**が送信するものと同じ合成テストリクエスト（`source.application`は`config-test`）をサーバーに送信します。このリクエストは他のリクエストと同様に署名され、ユーザーのコンテンツは含まれません。通常どおり応答してください。有効な判定（許可または拒否）が返されるとブレーカーがリセットされて適用が再開されます。Webhook障害の場合はブレーカーが作動したままとなり、チェックが継続されます。管理者はいつでもブレーカーをリセットでき、管理者による設定変更によって自動チェックは停止されます。[サーキットブレーカー](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-configuration#circuit-breaker)を参照してください。

プロンプトフレームとツール呼び出しフレームは1つのサーキットブレーカーを共有し、どちらの失敗もカウントされます。

各作動は、[Activity Feed](https://platform.claude.com/docs/ja/manage-claude/compliance-activity-feed)に`inference_hooks_circuit_breaker_tripped`アクティビティとして、作動ごとに1件記録されます。ブレーカーが作動している間は、リクエストごとのInference hooksアクティビティは記録されないため、作動アクティビティがフィード上で作動期間を示す唯一の記録となります。

### レイテンシ

適用を行うと、組織内のすべての管理対象リクエストの「latency」（レイテンシ）に、AIセキュリティサーバーのラウンドトリップが加わります。**Validate tool calls**が有効な場合、ツール呼び出しフレームを生成する応答もその判定を待機します。これはシャドウモードでも同様です。判定は高速に保ち、大規模な組織に展開する前にサーバーの負荷テストを行ってください。

### 送信元IPアドレス

AIセキュリティサーバーへのリクエストは`160.79.106.0/24`から送信されます。これは、Anthropicが公開している[送信IP範囲](https://platform.claude.com/docs/ja/api/ip-addresses)の一部です。許可リストにはこのブロックを追加してください。同じページに記載されている受信IP範囲にはこのブロックが含まれないため、そちらは使用しないでください。許可リストを使うとサーバーの露出を減らせますが、署名検証の代わりにはなりません。このブロックには、Inference hooks以外のAnthropicの送信トラフィックも含まれるためです。

## 前方互換性

プロトコルは、正しく記述されたサーバーを壊すことなく拡張されます。サーバーは次のものを無視する必要があります。

* いずれかのフレームの不明なトップレベルフィールド。
* `metadata`内の不明なキー。
* 新しい`source.application`値。
* 新しい`actor.type`値。`actor`は`type`で区別される共用体であり、現在送信される種類は`"user"`のみです。将来の種類では`type`が存在することのみが保証されます。
* 認識できない`type`を持つコンテンツブロック。
* `user`または`assistant`以外の`role`を持つメッセージ（今後の改訂で追加される可能性があります）。

認識できないブロックタイプやフィールドを理由にリクエストを拒否しないでください。既知のフィールドを読み取り、残りはスキップしてください。

今後、他のフックイベントタイプが導入される予定です。新しいイベントタイプは、フィールドをスキップするだけでは処理できない追加です。リクエストには引き続き判定が必要です。トップレベルの`type`が認識できない値である場合は、エラーステータスではなく許可の判定を返してください。エラー応答は[Webhook障害](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-endpoint#webhook-failures)であり、失敗が継続すると[サーキットブレーカー](https://platform.claude.com/docs/ja/manage-claude/inference-hooks-endpoint#circuit-breaker)が作動します。

## 統合を設計する

本番環境のAIセキュリティサーバーでは、通信プロトコル以外にもいくつかの設計上の判断が必要です。

**`webhook-id`で重複を排除する。** `webhook-id`ヘッダーは配信ごとに一意で、ボディの`request_id`と同じ値です。接続失敗時のリトライでも同じ値が使われるため、冪等性キーとして使用できます。判定を記録する場合は、この値をレコードのキーにしてください。

**判定を記録し、拒否と突き合わせる。** 返した各判定を、その`reference_id`とともに保存してください。すべての拒否は、サーバーが返した`reference_id`を含む`inference_hooks_request_denied`コンプライアンスアクティビティとして記録されます。これにより、[Activity Feed](https://platform.claude.com/docs/ja/manage-claude/compliance-activity-feed)の拒否を、自社システム内の対応するレコードと突き合わせることができます。

**常に許可するサーバーでアーカイブする。** トランスクリプトを制限せずにリアルタイムで取得するには、無条件に`{"action": "allow"}`を返し、応答した後にフレームを保存します。これは、[Compliance API](https://platform.claude.com/docs/ja/manage-claude/compliance-api)をポーリングする方法に代わるプッシュ型の手段です。保存する前に応答することで、サーバーとのやり取りがユーザーのクリティカルパスに影響しなくなります。

**`deny_reason`はエンドユーザー向けに書く。** 返したテキストは、リクエストがブロックされたときにユーザーに表示されます（500文字で切り詰められます）。自社チームにしか理解できないスキャナーのコードを出力するのではなく、どの種類のコンテンツを削除すべきかなど、ユーザーが何を変更すればよいかを伝えてください。

## 次のステップ

<CardGroup cols={2}>
  <Card title="Inference hooksを設定する" href="https://platform.claude.com/docs/ja/manage-claude/inference-hooks-configuration">
    Inference hooksを有効にし、エンドポイントを接続してテストし、適用、障害時の処理、展開を制御します。
  </Card>

  <Card title="Inference hooksの概要" href="https://platform.claude.com/docs/ja/manage-claude/inference-hooks">
    Inference hooksとは何か、判定のやり取りの仕組み、使用すべき場面について説明します。
  </Card>
</CardGroup>
