from line 158
158158| `User-Agent` | `anthropic-dlp/1` |
159159| `Accept-Encoding` | `identity` |
160160
161There is one hook event today: the prompt frame, sent once per governed inference request, before inference begins. Anthropic holds the request until your AI security server responds or the verdict timeout elapses.
161There are two hook events, told apart by the top-level `type` field. The prompt frame is sent once per governed inference request, before inference begins. The tool call frame is sent when a model response contains tool calls, before any of them runs, in organizations that have turned on **Validate tool calls**. Either way, Anthropic waits until your AI security server responds or the verdict timeout elapses.
162162
163163## The prompt frame
164164
165165The request body is a JSON object with these fields:
166166
167| Field | Type | Description |
168| ------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
169| `type` | string | The hook event. Always `"prompt"` today; other event types will be introduced in the future, so handle an unrecognized value gracefully (see [Forward compatibility](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#forward-compatibility)). |
170| `request_id` | string | Opaque per-inference-call identifier for correlation. Equals the `webhook-id` header. |
171| `tenant_id` | string or null | Opaque identifier for the organization the request belongs to. |
172| `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. |
173| `source` | object | The originating application: `application` (see [Source values](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#source-values)). |
174| `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. |
176| `model` | string or null | Public model identifier for this request, when available. |
177| `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. |
167| Field | Type | Description |
168| ------------ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
169| `type` | string | The hook event: `"prompt"` or `"tool_call"` (see [The tool call frame](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#the-tool-call-frame)). Other event types will be introduced in the future, so handle an unrecognized value gracefully (see [Forward compatibility](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#forward-compatibility)). |
170| `request_id` | string | Opaque per-frame identifier for correlation. Equals the `webhook-id` header. |
171| `tenant_id` | string or null | Opaque identifier for the organization the request belongs to. |
172| `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. |
173| `source` | object | The originating application: `application` (see [Source values](https://platform.claude.com/docs/en/manage-claude/inference-hooks-endpoint#source-values)). |
174| `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. |
176| `model` | string or null | Public model identifier for this request, when available. |
177| `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
179179An example request body:
180180
from line 219
219219
220220Each entry in `messages` has a `role` of `user` or `assistant` (tool results appear under the `user` role, matching the public Messages API content model) and a `content` array of blocks discriminated by `type`:
221221
222| Block `type` | Fields |
223| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
224| `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. |
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. |
222| Block `type` | Fields |
223| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
224| `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 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
229229Apart 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
from line 244
244244
245245Treat `source.application` as advisory routing metadata, not a trust boundary: don't rest a security-critical policy decision on it alone.
246246
247## The tool call frame
248
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.
250
251It differs from the prompt frame in three ways:
252
253* `type` is `"tool_call"`.
254* `messages` holds only the latest message, the `assistant` message Claude just produced: any `text` blocks and one `tool_use` block per 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.
255* Each `tool_use` block carries a `tool_info` object that says who provides the tool.
256
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.
258
259`tool_info` says who provides the tool, not who runs it or what it 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`.
260
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, so don't treat either as a trust boundary.
262
263### Platform tools
264
265A platform tool is one of the Claude API's predefined tools, such as web search or bash. It is `"platform"` whether Anthropic or the application that calls Claude runs it.
266
267| Field | Present | Description |
268| -------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
269| `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. |
271| `toolset_name` | Optional | The toolset the tool belongs to, such as `browser`. |
272
273### Application tools
274
275An application tool is one that the Anthropic application making the request provides itself, such as claude.ai's own tools. It is `"application"` whether Anthropic or the application that calls Claude runs it.
276
277| Field | Present | Description |
278| -------------- | -------- | --------------------------------- |
279| `type` | Always | `"application"` |
280| `toolset_name` | Optional | The group of tools it belongs to. |
281
282### Third-party tools
283
284A 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.
285
286| Field | Present | Description |
287| ----------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
288| `type` | Always | `"third_party"` |
289| `toolset_name` | Optional | The name the request gives the server. |
290| `origin` | Optional | The scheme, host, and non-default port of the server's URL. An absent `origin` means unknown, not safe. |
291| `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. |
292
293A `tool_use` block for a tool on an MCP server that the request names `crm` in `mcp_servers`:
294
295```json
296{
297 "type": "tool_use",
298 "id": "toolu_01GhIjKlMnOpQrStUvWxYzAb",
299 "tool_name": "crm_search",
300 "input": {
301 "query": "accounts renewing in Q4"
302 },
303 "tool_info": {
304 "type": "third_party",
305 "toolset_name": "crm",
306 "origin": "https://mcp.crm.example.com",
307 "verified_origin": true
308 }
309}
310```
311
312### Client tools
313
314A client tool is any other tool. The application that calls Claude declares it and receives its calls. A call to a tool the request doesn't declare is also `"client"`.
315
316| Field | Present | Description |
317| -------------- | -------- | --------------------------------- |
318| `type` | Always | `"client"` |
319| `toolset_name` | Optional | The group of tools it belongs to. |
320
321A `tool_use` block for a tool that the calling application declares:
322
323```json
324{
325 "type": "tool_use",
326 "id": "toolu_01AbCdEfGhIjKlMnOpQrStUv",
327 "tool_name": "read_file",
328 "input": {
329 "path": "reports/q3.txt"
330 },
331 "tool_info": {
332 "type": "client"
333 }
334}
335```
336
247337## Return a verdict
248338
249339Respond with HTTP 200 and a JSON verdict body for both outcomes; the `action` field discriminates. To allow the request:
from line 369
279369
280370Anthropic 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.
281371
372A tool call frame takes the same verdict body, and every rule in this section applies to it unchanged. `allow` lets the tool calls run and the response continues; `deny` stops all of them and ends the response with the same error as a denied prompt, including your `deny_reason`. Text delivered before the first tool call is not taken back.
373
282374## Verify the signature
283375
284376Requests are signed per the [Standard Webhooks](https://www.standardwebhooks.com/) specification, using three headers. Anthropic sends the header names in lowercase, and proxies are free to re-case them, so look them up case-insensitively.
from line 767
675767
676768### Timeout and retry
677769
678Your administrator sets a verdict timeout between 1 and 10,000ms (5,000ms by default). The budget covers the entire exchange: connection, TLS handshake, request, and response.
770Your administrator sets a verdict timeout between 1 and 10,000ms (5,000ms by default). The budget covers the entire exchange: connection, TLS handshake, request, and response. The same timeout applies to tool call frames.
679771
680772Anthropic retries exactly once, after a 100ms delay, and only when the connection attempt fails. The retry shares the same timeout budget and carries the same `webhook-id` and the same signature. Once your AI security server has responded, the exchange is never retried.
681773
from line 781
689781
690782Starting 10 minutes after the trip, Anthropic checks whether your server has recovered: at most about once per minute it sends your server the same synthetic test request that **Test connection** sends (`source.application` is `config-test`), signed like any other request and carrying no user content. Respond to it normally. A valid verdict, allow or deny, resets the breaker and enforcement resumes; a webhook failure leaves the breaker tripped, and the checks continue. An administrator can also reset the breaker at any time, and administrator configuration changes stop the automatic checks; see [Circuit breaker](https://platform.claude.com/docs/en/manage-claude/inference-hooks-configuration#circuit-breaker).
691783
784Prompt frames and tool call frames share one circuit breaker, and failures on either count toward it.
785
692786Each 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 per 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.
693787
694788### Latency
695789
696Enforcement adds your AI security server's round trip to the latency of every governed request in your organization. Keep the verdict fast, and load-test your server before rolling it out to a large organization.
790Enforcement 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.
697791
698792### Source IP addresses
699793
from line 797
703797
704798The protocol grows without breaking correctly written servers. Your server must ignore:
705799
706* Unknown top-level fields on the prompt frame.
800* Unknown top-level fields on either frame.
707801* Unknown keys in `metadata`.
708802* New `source.application` values.
709803* New `actor.type` values. `actor` is a union discriminated on `type`, and `"user"` is the only kind sent today; a future kind guarantees only that `type` is present.