inference-hooks-endpoint changedmanage-claude/inference-hooks-endpoint
Nearest release: v2.1.312, published under an hour before this site recorded the change. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Recorded here
Lines+55added
Lines−55removed
From line
148
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits20to this page, all time
The whole hunk
from line 148, old and new numbered
/
from line 148
148148
149149Anthropic sends an HTTPS `POST` to the URL your administrator configures. The whole configured URL is the endpoint: there is no fixed path suffix, so choose any path that suits your server.
150150
151Host your AI security server where Anthropic can reach it: an `https://` URL on port 443, on a publicly routable host. Private, loopback, and carrier-grade NAT ranges are refused at connect time. The server must have a certificate that validates against the public CA trust store, and it must respond without redirects. The host must have an IPv4 address, which Anthropic uses even when the host also has IPv6 addresses; a URL whose host is `localhost` or an IPv6 address is refused. The configured URL must be the final destination. Reverse-tunnel hosts (ngrok and similar tunnel services) are not supported: Anthropic's network policy blocks them. Host your server on a domain you control. [Configure Inference hooks](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration) covers how your administrator sets and tests the URL.
151Host your AI security server where Anthropic can reach it: an `https://` URL on port 443, on a publicly routable host. Private, loopback, and carrier-grade NAT ranges are refused at connect time. The server must have a certificate that validates against the public CA trust store, and it must respond without redirects. The host must have an IPv4 address, which Anthropic uses even when the host also has IPv6 addresses; a URL whose host is `localhost` or an IPv6 address is refused. The configured URL must be the final destination. Reverse-tunnel hosts (ngrok and similar tunnel services) are not supported: Anthropic’s network policy blocks them. Host your server on a domain you control. [Configure Inference hooks](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration) covers how your administrator sets and tests the URL.
152152
153153Every request carries these fixed headers, along with any [custom request headers](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration) your administrator configured and, once your organization has a signing secret, the `webhook-*` signature headers described in [Verify the signature](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#verify-the-signature):
154154
from line 172
172172| `actor` | object | The principal the request is attributed to, discriminated on `type` (`"user"` is the only value sent today): `id` (a tagged identifier, stable across requests for the same account) and `email_address` (when available). Both `id` and `email_address` can be null. |
173173| `source` | object | The originating application: `application` (see [Source values](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#source-values)). |
174174| `messages` | array | The conversation transcript up to the point of inference. See [Content blocks](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#content-blocks). |
175| `session_id` | string or null | Opaque conversation identifier, when one exists. Don't parse it. For Claude Code it is a best-effort, client-asserted session identifier. |
175| `session_id` | string or null | Opaque conversation identifier, when one exists. Don’t parse it. For Claude Code it is a best-effort, client-asserted session identifier. |
176176| `model` | string or null | Public model identifier for this request, when available. |
177177| `metadata` | object | Reserved extension map of string keys to string values, sent empty today. Require nothing from it, and tolerate its absence, its presence, and any keys that appear. |
178178
from line 222
222222| Block `type` | Fields |
223223| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
224224| `text` | `text`: the text content. |
225| `tool_use` | `id`: the identifier the matching tool result references. `tool_name`: the tool's name. `input`: the arguments the model passed to the tool. `tool_info`: on a tool call frame only (left out elsewhere, never `null`), an object that says who runs or provides the tool; see [The tool call frame](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#the-tool-call-frame). |
226| `tool_result` | `content`: the tool's output as text, with parts joined by newlines; binary parts such as images are replaced by placeholder markers, and raw bytes are never sent. `is_error`: whether the tool call failed. `tool_name`: the tool's name, so a policy can condition on tool identity without cross-referencing an earlier block. `tool_use_id`: the `id` of the matching `tool_use` block. |
227| `attachment` | `file_name`: the original file name or path. `media_type`: the attachment's media type. `size_bytes`: the size of the original file. `text`: the text content of the attachment when available, such as extracted document text, an audio transcript, or link metadata. Raw attachment bytes are never sent. |
225| `tool_use` | `id`: the identifier the matching tool result references. `tool_name`: the tool’s name. `input`: the arguments the model passed to the tool. `tool_info`: on a tool call frame only (left out elsewhere, never `null`), an object that says who runs or provides the tool; see [The tool call frame](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#the-tool-call-frame). |
226| `tool_result` | `content`: the tool’s output as text, with parts joined by newlines; binary parts such as images are replaced by placeholder markers, and raw bytes are never sent. `is_error`: whether the tool call failed. `tool_name`: the tool’s name, so a policy can condition on tool identity without cross-referencing an earlier block. `tool_use_id`: the `id` of the matching `tool_use` block. |
227| `attachment` | `file_name`: the original file name or path. `media_type`: the attachment’s media type. `size_bytes`: the size of the original file. `text`: the text content of the attachment when available, such as extracted document text, an audio transcript, or link metadata. Raw attachment bytes are never sent. |
228228
229Apart from `type`, a `text` block's `text`, and a `tool_result` block's `content` and `is_error`, any of these fields can be `null` when the value isn't known; for example, an image arrives as an `attachment` block with `file_name` and `text` set to `null`.
229Apart from `type`, a `text` block’s `text`, and a `tool_result` block’s `content` and `is_error`, any of these fields can be `null` when the value isn’t known; for example, an image arrives as an `attachment` block with `file_name` and `text` set to `null`.
230230
231A block whose `type` you don't recognize is a forward-compatible addition. The only field it guarantees is `type`; your policy may inspect whatever other fields are present, but must not reject the request because of an unrecognized type.
231A block whose `type` you don’t recognize is a forward-compatible addition. The only field it guarantees is `type`; your policy may inspect whatever other fields are present, but must not reject the request because of an unrecognized type.
232232
233233### What the transcript contains
234234
235The transcript is the conversation as the end user sees it, up to the point of inference: transcript text, tool calls and their results, extracted attachment text, and prior turns. It never includes system prompts, tool definitions, Anthropic-internal context, Claude's hidden reasoning, or raw file bytes.
235The transcript is the conversation as the end user sees it, up to the point of inference: transcript text, tool calls and their results, extracted attachment text, and prior turns. It never includes system prompts, tool definitions, Anthropic-internal context, Claude’s hidden reasoning, or raw file bytes.
236236
237A turn whose every block is excluded is omitted entirely, so don't assume strict user and assistant alternation.
237A turn whose every block is excluded is omitted entirely, so don’t assume strict user and assistant alternation.
238238
239Transcripts are sent untruncated, so a long conversation with large attachments produces a large request body. In practice the model's context window keeps bodies under about 10 MB, but the protocol allows up to 64 MiB. Several common defaults are much smaller, including nginx `client_max_body_size` at 1 MB and Express `express.json()` at 100 kB, and a rejected body counts as a webhook failure, so under **Allow the request** failure handling an oversized prompt would reach the model uninspected.
239Transcripts are sent untruncated, so a long conversation with large attachments produces a large request body. In practice the model’s context window keeps bodies under about 10 MB, but the protocol allows up to 64 MiB. Several common defaults are much smaller, including nginx `client_max_body_size` at 1 MB and Express `express.json()` at 100 kB, and a rejected body counts as a webhook failure, so under **Allow the request** failure handling an oversized prompt would reach the model uninspected.
240240
241241### Source values
242242
243`source.application` is an open string, not a closed enum. Common values are `claude-ai`, `claude-code`, and `cowork`; [connection tests](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration) and automatic circuit-breaker [recovery checks](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#circuit-breaker) use `config-test`. New values may appear, and your server must not reject a request because of one it doesn't recognize.
243`source.application` is an open string, not a closed enum. Common values are `claude-ai`, `claude-code`, and `cowork`; [connection tests](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration) and automatic circuit-breaker [recovery checks](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#circuit-breaker) use `config-test`. New values may appear, and your server must not reject a request because of one it doesn’t recognize.
244244
245Treat `source.application` as advisory routing metadata, not a trust boundary: don't rest a security-critical policy decision on it alone.
245Treat `source.application` as advisory routing metadata, not a trust boundary: don’t rest a security-critical policy decision on it alone.
246246
247247## The tool call frame
248248
249When Claude produces tool calls, Anthropic sends one tool call frame that lists them. Calls to some of claude.ai's own tools may be left out, and a response whose only tool calls are left out doesn't produce a tool call frame; see [Availability](https://platform.claude.com/docs/en/manage-claude/inference-hooks#availability). One verdict covers the whole frame: you can't allow some tool calls and deny others. The frame goes to the same endpoint as the prompt frame, with the same headers, signature, and top-level fields. Tool calls made by code that Claude runs in the code execution tool are sent the same way, in separate tool call frames, before they run.
249When Claude produces tool calls, Anthropic sends one tool call frame that lists them. Calls to some of claude.ai’s own tools may be left out, and a response whose only tool calls are left out doesn’t produce a tool call frame; see [Availability](https://platform.claude.com/docs/en/manage-claude/inference-hooks#availability). One verdict covers the whole frame: you can’t allow some tool calls and deny others. The frame goes to the same endpoint as the prompt frame, with the same headers, signature, and top-level fields. Tool calls made by code that Claude runs in the code execution tool are sent the same way, in separate tool call frames, before they run.
250250
251251It differs from the prompt frame in three ways:
252252
from line 254
254254* `messages` holds only the latest message, the `assistant` message Claude just produced: any `text` blocks and one `tool_use` block for each tool call the frame lists, in the order the model produced them. Earlier conversation is left out, because the prompt frame sent before that model call carried it. Read the last entry of `messages`, because the protocol may later add earlier messages before it.
255255* Each `tool_use` block carries a `tool_info` object that says who runs or provides the tool.
256256
257Where `session_id` is set, it is the same on both frames. The tool call frame has its own `request_id`, which is opaque like the prompt frame's.
257Where `session_id` is set, it is the same on both frames. The tool call frame has its own `request_id`, which is opaque like the prompt frame’s.
258258
259`tool_info` says who runs or provides the tool, not what the tool can reach. It is one of four kinds, told apart by its `type` field, and each kind carries its own fields. Anthropic sends the first of the following kinds that fits the tool. New kinds may appear: accept a `type` you don't recognize, and for such a kind rely only on `type`.
259`tool_info` says who runs or provides the tool, not what the tool can reach. It is one of four kinds, told apart by its `type` field, and each kind carries its own fields. Anthropic sends the first of the following kinds that fits the tool. New kinds may appear: accept a `type` you don’t recognize, and for such a kind rely only on `type`.
260260
261An optional field that doesn't apply is left out, never `null`, so a `tool_info` can be just `{"type": "client"}`. `tool_name` is chosen by whoever defined the tool, and a server's `toolset_name` by whoever wrote the request or named the connector. Don't treat either as a trust boundary.
261An optional field that doesn’t apply is left out, never `null`, so a `tool_info` can be just `{"type": "client"}`. `tool_name` is chosen by whoever defined the tool, and a server’s `toolset_name` by whoever wrote the request or named the connector. Don’t treat either as a trust boundary.
262262
263263### Platform tools
264264
from line 267
267267| Field | Present | Description |
268268| -------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
269269| `type` | Always | `"platform"` |
270| `tool_type` | Always | The tool's versioned type, such as `web_search_20250305`. Match it exactly; don't parse a name or a date out of it. |
270| `tool_type` | Always | The tool’s versioned type, such as `web_search_20250305`. Match it exactly; don’t parse a name or a date out of it. |
271271| `toolset_name` | Optional | The group of tools it belongs to. |
272272
273273### Application tools
274274
275An application tool is one that the Anthropic application making the request provides itself, such as claude.ai's own tools.
275An application tool is one that the Anthropic application making the request provides itself, such as claude.ai’s own tools.
276276
277277| Field | Present | Description |
278278| -------------- | -------- | --------------------------------- |
from line 295
295295}
296296```
297297
298claude.ai doesn't always name this tool `web_search`, so don't match on the name alone. `input` always includes the search query and can hold other fields.
298claude.ai doesn’t always name this tool `web_search`, so don’t match on the name alone. `input` always includes the search query and can hold other fields.
299299
300300### Third-party tools
301301
302A third-party tool is one on a server that Anthropic knows of, such as a claude.ai connector or an MCP server that the request names in `mcp_servers`. This doesn't mean Anthropic has vetted the server. In voice mode, a call to a claude.ai connector's tool is `"client"` instead.
302A third-party tool is one on a server that Anthropic knows of, such as a claude.ai connector or an MCP server that the request names in `mcp_servers`. This doesn’t mean Anthropic has vetted the server. In voice mode, a call to a claude.ai connector’s tool is `"client"` instead.
303303
304304| Field | Present | Description |
305305| ----------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
306306| `type` | Always | `"third_party"` |
307| `toolset_name` | Optional | For a server in `mcp_servers`, the name the request gives the server. For any other server, such as a claude.ai connector, the server's name, copied from `tool_name`. |
308| `origin` | Optional | The scheme, host, and non-default port of the server's URL. It can be a private or local address, `localhost` included. An absent `origin` means unknown, not safe. |
309| `verified_origin` | Whenever `origin` is | `true` only when Anthropic's servers connect to the server themselves. Treat `false` as an origin that Anthropic hasn't confirmed. |
307| `toolset_name` | Optional | For a server in `mcp_servers`, the name the request gives the server. For any other server, such as a claude.ai connector, the server’s name, copied from `tool_name`. |
308| `origin` | Optional | The scheme, host, and non-default port of the server’s URL. It can be a private or local address, `localhost` included. An absent `origin` means unknown, not safe. |
309| `verified_origin` | Whenever `origin` is | `true` only when Anthropic’s servers connect to the server themselves. Treat `false` as an origin that Anthropic hasn’t confirmed. |
310310
311311A `tool_use` block for a tool on an MCP server that the request names `crm` in `mcp_servers`:
312312
from line 346
346346}
347347```
348348
349For a claude.ai connector, `verified_origin` can be `false` even when Anthropic's servers connect to the server themselves.
349For a claude.ai connector, `verified_origin` can be `false` even when Anthropic’s servers connect to the server themselves.
350350
351A name copied from `tool_name` can differ from the name the user sees: `mcp__Google_Drive__search_files` gives `Google_Drive`, not `Google Drive`. It can also end in a short suffix (`Gmail-1a2b3c4d`), for example when another of the user's connectors has the same name. The name isn't a stable identifier and doesn't prove which server the tool is on. When `tool_name` has no server name in it, `toolset_name` is left out.
351A name copied from `tool_name` can differ from the name the user sees: `mcp__Google_Drive__search_files` gives `Google_Drive`, not `Google Drive`. It can also end in a short suffix (`Gmail-1a2b3c4d`), for example when another of the user’s connectors has the same name. The name isn’t a stable identifier and doesn’t prove which server the tool is on. When `tool_name` has no server name in it, `toolset_name` is left out.
352352
353353### Client tools
354354
355A client tool is any other tool, usually one that the application calling Claude runs. A tool that Anthropic defines but the calling application runs, such as bash or computer use, is a client tool too, and carries its versioned type in `tool_type`. A call to a tool the request doesn't declare is also `"client"`.
355A client tool is any other tool, usually one that the application calling Claude runs. A tool that Anthropic defines but the calling application runs, such as bash or computer use, is a client tool too, and carries its versioned type in `tool_type`. A call to a tool the request doesn’t declare is also `"client"`.
356356
357A tool on an MCP server that Claude Code connects to directly from the user's computer, such as a local MCP server, is a client tool. Its `tool_info` is `{"type": "client"}`, and its `tool_name` is the name Claude Code gives it, in the form `mcp__<server>__<tool>`.
357A tool on an MCP server that Claude Code connects to directly from the user’s computer, such as a local MCP server, is a client tool. Its `tool_info` is `{"type": "client"}`, and its `tool_name` is the name Claude Code gives it, in the form `mcp__<server>__<tool>`.
358358
359A deny stops the call before Claude Code runs it. The exchange between Claude Code and a local server doesn't pass through Anthropic, so your AI security server sees the tool's result only when Claude Code sends it back. Its text is then in a `tool_result` block in the next prompt frame.
359A deny stops the call before Claude Code runs it. The exchange between Claude Code and a local server doesn’t pass through Anthropic, so your AI security server sees the tool’s result only when Claude Code sends it back. Its text is then in a `tool_result` block in the next prompt frame.
360360
361361| Field | Present | Description |
362362| -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
363363| `type` | Always | `"client"` |
364| `tool_type` | Optional | The tool's versioned type, such as `bash_20250124`, when Anthropic defines the tool. Match it exactly; don't parse a name or a date out of it. |
364| `tool_type` | Optional | The tool’s versioned type, such as `bash_20250124`, when Anthropic defines the tool. Match it exactly; don’t parse a name or a date out of it. |
365365| `toolset_name` | Optional | The group of tools it belongs to, such as `browser`. |
366366
367367A `tool_use` block for a tool that the calling application declares:
from line 421
421421| -------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
422422| `action` | `"allow"` or `"deny"`; required | `allow` lets inference proceed; `deny` rejects it. |
423423| `deny_reason` | string or null; at most 500 characters, longer values truncated | Shown to the end user when `action` is `deny`; ignored on `allow`. |
424| `reference_id` | string or null; at most 50 characters from `[A-Za-z0-9._:/-]` | Your own identifier for this evaluation. It's recorded on the denial's `inference_hooks_request_denied` [compliance activity](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed) and never shown to the end user. Keep it opaque: no request content and no personal data. |
424| `reference_id` | string or null; at most 50 characters from `[A-Za-z0-9._:/-]` | Your own identifier for this evaluation. It’s recorded on the denial’s `inference_hooks_request_denied` [compliance activity](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed) and never shown to the end user. Keep it opaque: no request content and no personal data. |
425425
426426A deny is never discarded over a formatting problem: an oversize `deny_reason` is truncated, a malformed `reference_id` is silently dropped, and the `action` is still honored.
427427
428The reverse doesn't hold. Anything other than HTTP 200 with a parseable verdict is a webhook failure, and your organization's [failure handling](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration) applies instead of a verdict. In particular:
428The reverse doesn’t hold. Anything other than HTTP 200 with a parseable verdict is a webhook failure, and your organization’s [failure handling](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration) applies instead of a verdict. In particular:
429429
430* Don't signal a deny with an error status. A non-200 response is a failure, not a deny.
430* Don’t signal a deny with an error status. A non-200 response is a failure, not a deny.
431431* Any `action` value other than `allow` or `deny` is treated as a webhook failure.
432432
433433Anthropic reads at most 64 KiB of the response body, and the body must be uncompressed. Redirects are not followed, and cookies are ignored. Unknown fields in the verdict body are ignored, so you can return a richer object alongside the fields documented here.
from line 440
440440
441441| Header | Contents |
442442| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
443| `webhook-id` | Unique identifier for this delivery. Equals the body's `request_id`. Use it as an idempotency key and as the first component of the signed payload. |
444| `webhook-timestamp` | Unix time in seconds, as a decimal string, when the request was signed. Reject a timestamp more than 5 minutes from your server's clock, in either direction. |
443| `webhook-id` | Unique identifier for this delivery. Equals the body’s `request_id`. Use it as an idempotency key and as the first component of the signed payload. |
444| `webhook-timestamp` | Unix time in seconds, as a decimal string, when the request was signed. Reject a timestamp more than 5 minutes from your server’s clock, in either direction. |
445445| `webhook-signature` | One or more space-separated `v1,<base64>` values, each an HMAC-SHA256 over `{webhook-id}.{webhook-timestamp}.{raw body bytes}`. Accept the request if any value matches yours, using a constant-time comparison. |
446446
447447Two details cause most verification bugs:
from line 451
451451
452452Once your organization has a signing secret, every request Anthropic sends is signed, including the connection test, because the setup flow generates the secret before the first test. [Enabling Inference hooks requires a secret](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration), so reject any request that arrives unsigned. One exception: an organization that enabled Inference hooks before the secret was required keeps sending unsigned requests until its administrator generates one. Accept unsigned requests only until your administrator confirms the secret exists, then reject them.
453453
454[Rotating the secret](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration#rotate-your-signing-secret) is an immediate cutover, but requests signed with the previous secret can still arrive for about a minute afterward, plus anything already in flight. Have your AI security server accept signatures from both secrets during the switchover so those stragglers aren't rejected.
454[Rotating the secret](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration#rotate-your-signing-secret) is an immediate cutover, but requests signed with the previous secret can still arrive for about a minute afterward, plus anything already in flight. Have your AI security server accept signatures from both secrets during the switchover so those stragglers aren’t rejected.
455455
456The following samples are server implementations, so there is no shell tab: an AI security server is a long-running HTTPS service rather than a one-shot request. Each sample uses only the language's standard library; the [Standard Webhooks](https://www.standardwebhooks.com/) project also publishes verification libraries for most languages.
456The following samples are server implementations, so there is no shell tab: an AI security server is a long-running HTTPS service rather than a one-shot request. Each sample uses only the language’s standard library; the [Standard Webhooks](https://www.standardwebhooks.com/) project also publishes verification libraries for most languages.
457457
458458<CodeGroup exclude="shell">
459459 ```python Python
from line 836
836836
837837### Webhook failures
838838
839Timeouts, non-200 statuses (redirects included), unparseable or oversized response bodies, and unreachable endpoints are all webhook failures. A webhook failure never becomes a deny; instead, your organization's [failure handling](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration) setting determines whether the affected request is blocked or proceeds without inspection.
839Timeouts, non-200 statuses (redirects included), unparseable or oversized response bodies, and unreachable endpoints are all webhook failures. A webhook failure never becomes a deny; instead, your organization’s [failure handling](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration) setting determines whether the affected request is blocked or proceeds without inspection.
840840
841841### Circuit breaker
842842
from line 846
846846
847847Prompt frames and tool call frames share one circuit breaker, and failures on either count toward it.
848848
849Each trip is recorded as an `inference_hooks_circuit_breaker_tripped` activity in the [Activity Feed](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed), one activity for each trip. While the breaker is tripped, no per-request Inference hooks activities are recorded, so the trip activity is the feed's only record of the tripped window.
849Each trip is recorded as an `inference_hooks_circuit_breaker_tripped` activity in the [Activity Feed](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed), one activity for each trip. While the breaker is tripped, no per-request Inference hooks activities are recorded, so the trip activity is the feed’s only record of the tripped window.
850850
851851### Latency
852852
853Enforcement adds your AI security server's round trip to the latency of every governed request in your organization. With **Validate tool calls** on, a response that produces a tool call frame also waits for its verdict, in shadow mode as well. Keep the verdict fast, and load-test your server before rolling it out to a large organization.
853Enforcement adds your AI security server’s round trip to the latency of every governed request in your organization. With **Validate tool calls** on, a response that produces a tool call frame also waits for its verdict, in shadow mode as well. Keep the verdict fast, and load-test your server before rolling it out to a large organization.
854854
855855### Source IP addresses
856856
857Requests to your AI security server originate from `160.79.106.0/24`, part of Anthropic's published [outbound IP ranges](https://platform.claude.com/docs/en/api/ip-addresses). Allowlist that block, not the inbound ranges on the same page, which don't cover it. Allowlisting narrows your server's exposure, but it is not a substitute for signature verification: the block carries Anthropic egress traffic beyond Inference hooks.
857Requests to your AI security server originate from `160.79.106.0/24`, part of Anthropic’s published [outbound IP ranges](https://platform.claude.com/docs/en/api/ip-addresses). Allowlist that block, not the inbound ranges on the same page, which don’t cover it. Allowlisting narrows your server’s exposure, but it is not a substitute for signature verification: the block carries Anthropic egress traffic beyond Inference hooks.
858858
859859## Forward compatibility
860860
from line 869
869869
870870Never reject a request because of an unrecognized block type or field; read the fields you know and skip the rest.
871871
872Other hook event types will be introduced in the future. A new event type is an addition your server can't handle by skipping a field: the request still needs a verdict. When the top-level `type` is a value you don't recognize, return an allow verdict rather than an error status; an error response is a [webhook failure](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#webhook-failures), and sustained failures trip the [circuit breaker](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#circuit-breaker).
872Other hook event types will be introduced in the future. A new event type is an addition your server can’t handle by skipping a field: the request still needs a verdict. When the top-level `type` is a value you don’t recognize, return an allow verdict rather than an error status; an error response is a [webhook failure](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#webhook-failures), and sustained failures trip the [circuit breaker](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#circuit-breaker).
873873
874874## Design your integration
875875
876876A production AI security server makes a few design choices beyond the wire protocol.
877877
878**Deduplicate on `webhook-id`.** The `webhook-id` header is unique for each delivery and equals the body's `request_id`, and a connection-failure retry reuses it, so it works as an idempotency key. If you record verdicts, key the records on it.
878**Deduplicate on `webhook-id`.** The `webhook-id` header is unique for each delivery and equals the body’s `request_id`, and a connection-failure retry reuses it, so it works as an idempotency key. If you record verdicts, key the records on it.
879879
880880**Record verdicts and join denials.** Store each verdict you return along with its `reference_id`. Every denial is recorded as an `inference_hooks_request_denied` compliance activity carrying the `reference_id` your server returned, so you can join denials in the [Activity Feed](https://platform.claude.com/docs/en/manage-claude/compliance-activity-feed) to the matching records in your own system.
881881
882**Archive with an always-allow server.** To capture transcripts in real time without policing them, return `{"action": "allow"}` unconditionally and persist the frame after responding. This is a push-based alternative to polling the [Compliance API](https://platform.claude.com/docs/en/manage-claude/compliance-api), and answering before you persist keeps your round trip out of the user's critical path.
882**Archive with an always-allow server.** To capture transcripts in real time without policing them, return `{"action": "allow"}` unconditionally and persist the frame after responding. This is a push-based alternative to polling the [Compliance API](https://platform.claude.com/docs/en/manage-claude/compliance-api), and answering before you persist keeps your round trip out of the user’s critical path.
883883
884884**Write `deny_reason` for the end user.** The text you return is what the user sees when their request is blocked, truncated at 500 characters. Tell them what to change, such as which kind of content to remove, rather than emitting a scanner code that only your team can interpret.
885885
No line in this hunk matches that.