---
title: Sviluppare un'integrazione Inference hooks
url: https://platform.claude.com/docs/it/manage-claude/inference-hooks-endpoint
description: Costruisci il server di sicurezza AI che riceve le richieste Inference hooks firmate, le verifica e restituisce verdetti di autorizzazione o rifiuto.
---

<Note>
  Gli Inference hooks sono in beta e disponibili per le organizzazioni Claude Enterprise. I nomi dei campi, le forme delle richieste e gli header potrebbero cambiare durante la beta.
</Note>

Un'integrazione Inference hooks è un "AI security server" (server di sicurezza AI): un servizio HTTPS che Anthropic chiama. Per ogni richiesta governata, il tuo server riceve una `POST` firmata contenente la trascrizione della conversazione e risponde con un verdetto di autorizzazione (allow) o rifiuto (deny). Questa pagina documenta il protocollo per costruire quel server: gli schemi della richiesta e del verdetto, la verifica della firma e il contratto operativo.

Per attivare gli Inference hooks e indirizzarli al tuo endpoint, consulta [Configurare gli Inference hooks](https://platform.claude.com/docs/it/manage-claude/inference-hooks-configuration). Per capire cosa sono gli Inference hooks e quando usarli, consulta la [panoramica degli Inference hooks](https://platform.claude.com/docs/it/manage-claude/inference-hooks).

## Ottenere un primo round trip del verdetto

L'integrazione funzionante più piccola è un server che legge ogni richiesta e la autorizza. Esegui uno dei seguenti server, esponilo a un URL pubblico `https://` (ad esempio, dietro un reverse proxy con terminazione TLS su un host che controlli, non un servizio di reverse tunnel; vedi [Ricevere una richiesta](https://platform.claude.com/docs/it/manage-claude/inference-hooks-endpoint#receive-a-request)), quindi chiedi al tuo amministratore di [impostarlo come endpoint e testare la connessione](https://platform.claude.com/docs/it/manage-claude/inference-hooks-configuration): il risultato di **Test connection** riporta il verdetto allow restituito dal tuo server.

<CodeGroup exclude="shell">
  ```python Python
  # Esegui con: 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):
          # Svuota il body; le trascrizioni possono essere di diversi megabyte.
          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
  // Esegui con: node server.ts
  import { createServer } from "node:http";

  createServer((request, response) => {
    // Svuota il body prima di rispondere; le trascrizioni possono essere di diversi megabyte.
    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
  // Esegui con: dotnet run server.cs

  var app = WebApplication.Create();

  app.MapPost("/{**path}", async (HttpRequest request) =>
  {
      // Svuota il body; le trascrizioni possono essere di diversi megabyte.
      await request.Body.CopyToAsync(Stream.Null);
      return Results.Text("""{"action": "allow"}""", "application/json");
  });

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

  ```go Go
  // Esegui con: go run server.go
  package main

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

  func main() {
  	http.HandleFunc("POST /", func(writer http.ResponseWriter, request *http.Request) {
  		// Svuota il body per poter riutilizzare la connessione; le trascrizioni possono essere di megabyte.
  		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
  // Esegui con: java VerdictServer.java
  import com.sun.net.httpserver.HttpServer;

  void main() throws IOException {
      HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
      server.createContext("/", exchange -> {
          // Svuota il body senza bufferizzarlo; le trascrizioni possono essere di megabyte.
          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
  // Esegui con: php -S 0.0.0.0:8000 server.php

  // Svuota il body; le trascrizioni possono essere di diversi megabyte.
  file_get_contents('php://input');

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

  ```ruby Ruby
  # webrick è una gem normale in Ruby 3.4: gem install webrick, oppure aggiungi gem "webrick".
  # Esegui con: 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>
  Questi server accettano ogni richiesta, incluse quelle non firmate. Aggiungi la [verifica della firma](https://platform.claude.com/docs/it/manage-claude/inference-hooks-endpoint#verify-the-signature) prima di applicare l'enforcement.
</Note>

## Ricevere una richiesta

Anthropic invia una `POST` HTTPS all'URL configurato dal tuo amministratore. L'intero URL configurato è l'endpoint: non esiste un suffisso di percorso fisso, quindi scegli qualsiasi percorso adatto al tuo server.

Ospita il tuo server di sicurezza AI dove Anthropic può raggiungerlo: un URL `https://` sulla porta 443, su un host instradabile pubblicamente (gli intervalli privati, di loopback e di carrier-grade NAT vengono rifiutati al momento della connessione), con un certificato che viene convalidato rispetto al trust store delle CA pubbliche, e che risponda senza reindirizzamenti. L'host deve avere un indirizzo IPv4, che Anthropic usa anche quando l'host ha anche indirizzi IPv6; un URL il cui host è `localhost` o un indirizzo IPv6 viene rifiutato. L'URL configurato deve essere la destinazione finale. Gli host di reverse tunnel (ngrok e servizi di tunnel simili) non sono supportati: la policy di rete di Anthropic li blocca. Ospita il tuo server su un dominio che controlli. [Configurare gli Inference hooks](https://platform.claude.com/docs/it/manage-claude/inference-hooks-configuration) spiega come il tuo amministratore imposta e testa l'URL.

Ogni richiesta contiene questi header fissi, insieme a eventuali [header di richiesta personalizzati](https://platform.claude.com/docs/it/manage-claude/inference-hooks-configuration) configurati dal tuo amministratore e, una volta che la tua organizzazione dispone di un segreto di firma, gli header di firma `webhook-*` descritti in [Verificare la firma](https://platform.claude.com/docs/it/manage-claude/inference-hooks-endpoint#verify-the-signature):

| Header            | Valore             |
| ----------------- | ------------------ |
| `Content-Type`    | `application/json` |
| `User-Agent`      | `anthropic-dlp/1`  |
| `Accept-Encoding` | `identity`         |

Esistono due eventi hook, distinti dal campo `type` di primo livello. Il frame del prompt viene inviato una volta per ogni richiesta di inferenza governata, prima che l'inferenza inizi. Il frame della chiamata a strumento viene inviato quando una risposta del modello contiene chiamate a strumenti, prima che una qualsiasi di esse venga eseguita, nelle organizzazioni che hanno attivato **Validate tool calls**. In entrambi i casi, Anthropic attende finché il tuo server di sicurezza AI non risponde o finché non scade il timeout del verdetto.

## Il prompt frame

Il corpo della richiesta è un oggetto JSON con questi campi:

| Campo        | Tipo          | Descrizione                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ------------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`       | string        | L'evento hook: `"prompt"` o `"tool_call"` (consulta [Il frame della chiamata a strumento](https://platform.claude.com/docs/it/manage-claude/inference-hooks-endpoint#the-tool-call-frame)). Altri tipi di evento verranno introdotti in futuro, quindi gestisci con tolleranza un valore non riconosciuto (consulta [Compatibilità futura](https://platform.claude.com/docs/it/manage-claude/inference-hooks-endpoint#forward-compatibility)). |
| `request_id` | string        | Identificatore opaco per singolo frame per la correlazione. Corrisponde all'header `webhook-id`.                                                                                                                                                                                                                                                                                                                                               |
| `tenant_id`  | string o null | Identificatore opaco dell'organizzazione a cui appartiene la richiesta.                                                                                                                                                                                                                                                                                                                                                                        |
| `actor`      | object        | Il principal a cui è attribuita la richiesta, discriminato su `type` (`"user"` è l'unico valore inviato oggi): `id` (un identificatore con tag, stabile tra le richieste per lo stesso account) e `email_address` (quando disponibile). Sia `id` che `email_address` possono essere null.                                                                                                                                                      |
| `source`     | object        | L'applicazione di origine: `application` (vedi [Valori di source](https://platform.claude.com/docs/it/manage-claude/inference-hooks-endpoint#source-values)).                                                                                                                                                                                                                                                                                  |
| `messages`   | array         | La trascrizione della conversazione fino al punto dell'inferenza. Vedi [Blocchi di contenuto](https://platform.claude.com/docs/it/manage-claude/inference-hooks-endpoint#content-blocks).                                                                                                                                                                                                                                                      |
| `session_id` | string o null | Identificatore opaco della conversazione, quando esiste. Non analizzarlo. Per Claude Code è un identificatore di sessione best-effort, asserito dal client.                                                                                                                                                                                                                                                                                    |
| `model`      | string o null | Identificatore pubblico del modello per questa richiesta, quando disponibile.                                                                                                                                                                                                                                                                                                                                                                  |
| `metadata`   | object        | Mappa di estensione riservata da chiavi stringa a valori stringa, oggi inviata vuota. Non richiedere nulla da essa e tollera la sua assenza, la sua presenza e qualsiasi chiave che compaia.                                                                                                                                                                                                                                                   |

Un esempio di corpo della richiesta:

```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": {}
}
```

### Blocchi di contenuto

Ogni voce in `messages` ha un `role` di `user` o `assistant` (i risultati degli strumenti compaiono sotto il ruolo `user`, in linea con il modello di contenuto della Messages API pubblica) e un array `content` di blocchi discriminati per `type`:

| `type` del blocco | Campi                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `text`            | `text`: il contenuto testuale.                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `tool_use`        | `id`: l'identificatore a cui fa riferimento il risultato dello strumento corrispondente. `tool_name`: il nome dello strumento. `input`: gli argomenti che il modello ha passato allo strumento. `tool_info`: solo in un frame della chiamata a strumento (omesso altrove, mai `null`), un oggetto che indica chi esegue o fornisce lo strumento; consulta [Il frame della chiamata a strumento](https://platform.claude.com/docs/it/manage-claude/inference-hooks-endpoint#the-tool-call-frame).         |
| `tool_result`     | `content`: l'output dello strumento come testo, con le parti unite da caratteri di nuova riga; le parti binarie come le immagini sono sostituite da marcatori segnaposto e i byte grezzi non vengono mai inviati. `is_error`: se la chiamata allo strumento è fallita. `tool_name`: il nome dello strumento, così che una policy possa condizionarsi sull'identità dello strumento senza fare riferimento incrociato a un blocco precedente. `tool_use_id`: l'`id` del blocco `tool_use` corrispondente. |
| `attachment`      | `file_name`: il nome o percorso originale del file. `media_type`: il media type dell'allegato. `size_bytes`: la dimensione del file originale. `text`: il contenuto testuale dell'allegato quando disponibile, come il testo estratto da un documento, una trascrizione audio o i metadati di un link. I byte grezzi degli allegati non vengono mai inviati.                                                                                                                                             |

Ad eccezione di `type`, del `text` di un blocco `text` e di `content` e `is_error` di un blocco `tool_result`, ognuno di questi campi può essere `null` quando il valore non è noto; ad esempio, un'immagine arriva come blocco `attachment` con `file_name` e `text` impostati su `null`.

Un blocco il cui `type` non riconosci è un'aggiunta compatibile con il futuro. L'unico campo che garantisce è `type`; la tua policy può ispezionare qualsiasi altro campo presente, ma non deve rifiutare la richiesta a causa di un tipo non riconosciuto.

### Cosa contiene la trascrizione

La trascrizione è la conversazione così come la vede l'utente finale, fino al punto dell'inferenza: testo della trascrizione, chiamate agli strumenti e relativi risultati, testo estratto dagli allegati e turni precedenti. Non include mai prompt di sistema, definizioni degli strumenti, contesto interno di Anthropic, il ragionamento nascosto di Claude o byte grezzi dei file.

Un turno i cui blocchi sono tutti esclusi viene omesso interamente, quindi non presumere una rigida alternanza tra user e assistant.

Le trascrizioni vengono inviate senza troncamenti, quindi una conversazione lunga con allegati di grandi dimensioni produce un corpo della richiesta di grandi dimensioni. In pratica la "context window" (finestra di contesto) del modello mantiene i corpi al di sotto di circa 10 MB, ma il protocollo consente fino a 64 MiB. Diversi valori predefiniti comuni sono molto più piccoli, tra cui `client_max_body_size` di nginx a 1 MB e `express.json()` di Express a 100 kB, e un corpo rifiutato conta come errore del webhook, quindi con la gestione degli errori **Allow the request** un prompt di dimensioni eccessive raggiungerebbe il modello senza essere ispezionato.

### Valori di source

`source.application` è una stringa aperta, non un enum chiuso. I valori comuni sono `claude-ai`, `claude-code` e `cowork`; i [test di connessione](https://platform.claude.com/docs/it/manage-claude/inference-hooks-configuration) e i [controlli di ripristino](https://platform.claude.com/docs/it/manage-claude/inference-hooks-endpoint#circuit-breaker) automatici del circuit breaker usano `config-test`. Potrebbero comparire nuovi valori e il tuo server non deve rifiutare una richiesta a causa di un valore che non riconosce.

Tratta `source.application` come metadato di instradamento indicativo, non come un confine di fiducia: non basare una decisione di policy critica per la sicurezza solo su di esso.

## Il frame della chiamata a strumento

Quando Claude produce chiamate a strumenti, Anthropic invia un frame della chiamata a strumento che le elenca. Le chiamate ad alcuni strumenti propri di claude.ai possono essere omesse, e una risposta le cui uniche chiamate a strumenti sono omesse non produce un frame della chiamata a strumento; consulta [Disponibilità](https://platform.claude.com/docs/it/manage-claude/inference-hooks#availability). Un solo verdetto copre l'intero frame: non puoi consentire alcune chiamate a strumenti e rifiutarne altre. Il frame viene inviato allo stesso endpoint del frame del prompt, con gli stessi header, la stessa firma e gli stessi campi di primo livello. Le chiamate a strumenti effettuate dal codice che Claude esegue nello strumento di esecuzione del codice vengono inviate allo stesso modo, in frame della chiamata a strumento separati, prima di essere eseguite.

Differisce dal frame del prompt in tre modi:

* `type` è `"tool_call"`.
* `messages` contiene solo l'ultimo messaggio, il messaggio `assistant` che Claude ha appena prodotto: eventuali blocchi `text` e un blocco `tool_use` per ogni chiamata a strumento elencata nel frame, nell'ordine in cui il modello le ha prodotte. La conversazione precedente viene omessa, perché il frame del prompt inviato prima di quella chiamata al modello la conteneva. Leggi l'ultima voce di `messages`, perché il protocollo potrebbe in futuro aggiungere messaggi precedenti prima di essa.
* Ogni blocco `tool_use` include un oggetto `tool_info` che indica chi esegue o fornisce lo strumento.

Quando `session_id` è impostato, è lo stesso in entrambi i frame. Il frame della chiamata a strumento ha il proprio `request_id`, che è opaco come quello del frame del prompt.

`tool_info` indica chi esegue o fornisce lo strumento, non cosa lo strumento può raggiungere. È di uno tra quattro tipi, distinti dal campo `type`, e ogni tipo ha i propri campi. Anthropic invia il primo dei seguenti tipi adatto allo strumento. Possono comparire nuovi tipi: accetta un `type` che non riconosci e, per un tipo del genere, fai affidamento solo su `type`.

Un campo facoltativo che non si applica viene omesso, mai impostato a `null`, quindi un `tool_info` può essere semplicemente `{"type": "client"}`. `tool_name` è scelto da chi ha definito lo strumento, e il `toolset_name` di un server da chi ha scritto la richiesta, quindi non trattare nessuno dei due come un confine di fiducia.

### Strumenti della piattaforma

Uno strumento della piattaforma è uno strumento che la Claude API stessa esegue mentre serve la richiesta, come la ricerca web o l'esecuzione del codice.

| Campo          | Presente    | Descrizione                                                                                                                 |
| -------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------- |
| `type`         | Sempre      | `"platform"`                                                                                                                |
| `tool_type`    | Sempre      | Il tipo con versione dello strumento, come `web_search_20250305`. Confrontalo esattamente; non estrarne un nome o una data. |
| `toolset_name` | Facoltativo | Il gruppo di strumenti a cui appartiene.                                                                                    |

### Strumenti dell'applicazione

Uno strumento dell'applicazione è uno strumento fornito direttamente dall'applicazione Anthropic che effettua la richiesta, come gli strumenti propri di claude.ai.

| Campo          | Presente    | Descrizione                              |
| -------------- | ----------- | ---------------------------------------- |
| `type`         | Sempre      | `"application"`                          |
| `toolset_name` | Facoltativo | Il gruppo di strumenti a cui appartiene. |

Un blocco `tool_use` per uno strumento di ricerca web che claude.ai fornisce direttamente:

```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 non assegna sempre a questo strumento il nome `web_search`, quindi non basarti solo sul nome. `input` include sempre la query di ricerca e può contenere altri campi.

### Strumenti di terze parti

Uno strumento di terze parti è uno strumento su un server di cui Anthropic è a conoscenza, come un connettore di claude.ai o un server MCP che la richiesta indica in `mcp_servers`. Ciò non significa che Anthropic abbia verificato il server.

| Campo             | Presente                   | Descrizione                                                                                                                                                                             |
| ----------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`            | Sempre                     | `"third_party"`                                                                                                                                                                         |
| `toolset_name`    | Facoltativo                | Il nome che la richiesta assegna al server.                                                                                                                                             |
| `origin`          | Facoltativo                | Lo schema, l'host e la porta non predefinita dell'URL del server. Può essere un indirizzo privato o locale, `localhost` incluso. Un `origin` assente significa sconosciuto, non sicuro. |
| `verified_origin` | Quando è presente `origin` | `true` solo quando sono i server di Anthropic stessi a connettersi al server. Tratta `false` come un'origine che Anthropic non ha confermato.                                           |

Un blocco `tool_use` per uno strumento su un server MCP che la richiesta chiama `crm` in `mcp_servers`:

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

### Strumenti client

Uno strumento client è qualsiasi altro strumento, di solito uno eseguito dall'applicazione che chiama Claude. Anche uno strumento definito da Anthropic ma eseguito dall'applicazione chiamante, come bash o computer use, è uno strumento client e riporta il suo tipo con versione in `tool_type`. Anche una chiamata a uno strumento che la richiesta non dichiara è `"client"`.

Uno strumento su un server MCP a cui Claude Code si connette direttamente dal computer dell'utente, come un server MCP locale, è uno strumento client. Il suo `tool_info` è `{"type": "client"}` e il suo `tool_name` è il nome che Claude Code gli assegna, nella forma `mcp__<server>__<tool>`.

Un rifiuto blocca la chiamata prima che Claude Code la esegua. Lo scambio tra Claude Code e un server locale non passa attraverso Anthropic, quindi il tuo server di sicurezza AI vede il risultato dello strumento solo quando Claude Code lo rimanda indietro. Il suo testo si trova quindi in un blocco `tool_result` nel frame del prompt successivo.

| Campo          | Presente    | Descrizione                                                                                                                                                        |
| -------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`         | Sempre      | `"client"`                                                                                                                                                         |
| `tool_type`    | Facoltativo | Il tipo con versione dello strumento, come `bash_20250124`, quando lo strumento è definito da Anthropic. Confrontalo esattamente; non estrarne un nome o una data. |
| `toolset_name` | Facoltativo | Il gruppo di strumenti a cui appartiene, come `browser`.                                                                                                           |

Un blocco `tool_use` per uno strumento dichiarato dall'applicazione chiamante:

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

Un blocco `tool_use` per bash, che Anthropic definisce e l'applicazione chiamante esegue:

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

## Restituire un verdetto

Rispondi con HTTP 200 e un corpo JSON del verdetto per entrambi gli esiti; il campo `action` discrimina. Per autorizzare la richiesta:

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

Per rifiutarla:

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

| Campo          | Vincoli                                                                       | Semantica                                                                                                                                                                                                                                                                                                                                     |
| -------------- | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `action`       | `"allow"` o `"deny"`; obbligatorio                                            | `allow` lascia procedere l'inferenza; `deny` la rifiuta.                                                                                                                                                                                                                                                                                      |
| `deny_reason`  | string o null; al massimo 500 caratteri, i valori più lunghi vengono troncati | Mostrato all'utente finale quando `action` è `deny`; ignorato su `allow`.                                                                                                                                                                                                                                                                     |
| `reference_id` | string o null; al massimo 50 caratteri da `[A-Za-z0-9._:/-]`                  | Il tuo identificatore per questa valutazione. Viene registrato nell'[attività di conformità](https://platform.claude.com/docs/it/manage-claude/compliance-activity-feed) `inference_hooks_request_denied` del rifiuto e non viene mai mostrato all'utente finale. Mantienilo opaco: nessun contenuto della richiesta e nessun dato personale. |

Un deny non viene mai scartato per un problema di formattazione: un `deny_reason` sovradimensionato viene troncato, un `reference_id` malformato viene silenziosamente eliminato e l'`action` viene comunque rispettata.

Il contrario non vale. Qualsiasi cosa diversa da HTTP 200 con un verdetto analizzabile è un fallimento del webhook, e si applica la [gestione dei fallimenti](https://platform.claude.com/docs/it/manage-claude/inference-hooks-configuration) della tua organizzazione invece di un verdetto. In particolare:

* Non segnalare un deny con uno stato di errore. Una risposta non-200 è un fallimento, non un deny.
* Qualsiasi valore di `action` diverso da `allow` o `deny` viene trattato come un fallimento del webhook.

Anthropic legge al massimo 64 KiB del corpo della risposta, e il corpo deve essere non compresso. I redirect non vengono seguiti e i cookie vengono ignorati. I campi sconosciuti nel corpo del verdetto vengono ignorati, quindi puoi restituire un oggetto più ricco insieme ai campi documentati qui.

Un frame della chiamata a strumento accetta lo stesso corpo del verdetto, e ogni regola di questa sezione vi si applica invariata. `allow` consente l'esecuzione delle chiamate a strumenti e la risposta prosegue; `deny` le blocca tutte e termina la risposta con lo stesso errore di un prompt rifiutato, incluso il tuo `deny_reason`. Il testo consegnato prima della prima chiamata a strumento non viene ritirato.

## Verificare la firma

Le richieste sono firmate secondo la specifica [Standard Webhooks](https://www.standardwebhooks.com/), usando tre header. Anthropic invia i nomi degli header in minuscolo e i proxy sono liberi di cambiarne le maiuscole, quindi cercali senza distinzione tra maiuscole e minuscole.

| Header              | Contenuto                                                                                                                                                                                                                                    |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `webhook-id`        | Identificatore univoco per questa consegna. Uguale al `request_id` del corpo. Usalo come chiave di idempotenza e come primo componente del payload firmato.                                                                                  |
| `webhook-timestamp` | Tempo Unix in secondi, come stringa decimale, del momento in cui la richiesta è stata firmata. Rifiuta un timestamp che si discosti di più di cinque minuti dall'orologio del tuo server, in entrambe le direzioni.                          |
| `webhook-signature` | Uno o più valori `v1,<base64>` separati da spazi, ciascuno un HMAC-SHA256 su `{webhook-id}.{webhook-timestamp}.{raw body bytes}`. Accetta la richiesta se uno qualsiasi dei valori corrisponde al tuo, usando un confronto a tempo costante. |

Due dettagli causano la maggior parte dei bug di verifica:

* **Verifica i byte grezzi.** Calcola l'HMAC sul corpo esattamente come ricevuto, prima di qualsiasi parsing JSON o ricodifica.
* **Decodifica il segreto con un decoder base64 standard.** Il segreto di firma è il valore dopo il prefisso `whsec_`, codificato con l'alfabeto base64 standard (`+` e `/`), così come la firma nell'header. Un decoder URL-safe deriva byte di chiave errati ogni volta che il segreto contiene `+` o `/`, cioè la maggior parte delle volte.

Una volta che la tua organizzazione dispone di un segreto di firma, ogni richiesta inviata da Anthropic è firmata, incluso il test di connessione, perché il flusso di configurazione genera il segreto prima del primo test. [L'abilitazione degli Inference hooks richiede un segreto](https://platform.claude.com/docs/it/manage-claude/inference-hooks-configuration), quindi rifiuta qualsiasi richiesta che arrivi non firmata. Un'eccezione: un'organizzazione che ha abilitato gli Inference hooks prima che il segreto fosse obbligatorio continua a inviare richieste non firmate finché il suo amministratore non ne genera uno. Accetta richieste non firmate solo finché il tuo amministratore non conferma che il segreto esiste, quindi rifiutale.

[Ruotare il segreto](https://platform.claude.com/docs/it/manage-claude/inference-hooks-configuration#rotate-your-signing-secret) è un passaggio immediato, ma le richieste firmate con il segreto precedente possono ancora arrivare per circa un minuto dopo, oltre a tutto ciò che è già in transito. Fai in modo che il tuo server di sicurezza AI accetti firme da entrambi i segreti durante il passaggio, così che quei ritardatari non vengano rifiutati.

I seguenti esempi sono implementazioni server, quindi non c'è una scheda shell: un server di sicurezza AI è un servizio HTTPS a lunga esecuzione piuttosto che una richiesta singola. Ogni esempio usa solo la libreria standard del linguaggio; il progetto [Standard Webhooks](https://www.standardwebhooks.com/) pubblica anche librerie di verifica per la maggior parte dei linguaggi.

<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()
      )

      # Confronta i byte: compare_digest su str solleva un'eccezione con input non 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>
      /// Restituisce true se il corpo è stato firmato da Anthropic per questa organizzazione.
      /// Anthropic invia i nomi delle intestazioni in minuscolo, ma i proxy possono
      /// modificarne le maiuscole, quindi confrontali senza distinzione tra maiuscole e minuscole.
      /// </summary>
      public static bool Verify(string secret, IReadOnlyDictionary<string, string> headers, byte[] body)
      {
          // TryAdd mantiene il primo valore se un proxy ha consegnato nomi duplicati
          // per maiuscole/minuscole; il costruttore di copia invece genererebbe un'eccezione.
          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
          }

          // Alfabeto base64 standard: un decoder URL-safe ricava byte della chiave errati.
          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 è a tempo costante e restituisce false se le lunghezze non corrispondono.
          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 indica se body è stato firmato da Anthropic per questa organizzazione.
  // net/http canonicalizza i nomi degli header alla ricerca, quindi i nomi con maiuscole diverse corrispondono comunque.
  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
  	}

  	// Alfabeto base64 standard: un decoder URL-safe ricava byte della chiave errati.
  	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
          }

          // Alfabeto base64 standard: un decoder URL-safe ricava byte della chiave errati.
          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) {
              // Ogni JVM include HmacSHA256, quindi questo non si attiva mai a runtime.
              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
      }

      // Alfabeto base64 standard: un decoder URL-safe ricava byte della chiave errati.
      $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
  # base64 è una gem inclusa in Ruby 3.4: le app gestite da Bundler aggiungono gem "base64".
  require "base64"
  require "openssl"

  TOLERANCE_SECONDS = 300

  # Restituisce true se il corpo è stato firmato da Anthropic per questa organizzazione.
  #
  # Anthropic invia i nomi degli header in minuscolo, ma i proxy sono liberi di
  # modificarne le maiuscole, quindi normalizza la ricerca in minuscolo.
  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

    # Alfabeto base64 standard: un decoder URL-safe ricava byte della chiave errati.
    begin
      key = Base64.strict_decode64(secret.delete_prefix("whsec_"))
    rescue ArgumentError
      return false # misconfigured secret: reject rather than crash
    end

    # Passa il corpo separatamente così la sua codifica non deve mai corrispondere a quella del prefisso.
    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 solleva un errore se le lunghezze differiscono, quindi verificale prima.
      candidate.bytesize == expected.bytesize &&
        OpenSSL.fixed_length_secure_compare(candidate, expected)
    end
  end
  ```
</CodeGroup>

## Semantica operativa

### Timeout e retry

Il tuo amministratore imposta un timeout del verdetto compreso tra 1 e 10.000 ms (5.000 ms per impostazione predefinita). Il budget copre l'intero scambio: connessione, handshake TLS, richiesta e risposta. Lo stesso timeout si applica ai frame della chiamata a strumento.

Anthropic ritenta esattamente una volta, dopo un ritardo di 100 ms, e solo quando il tentativo di connessione fallisce. Il retry condivide lo stesso budget di timeout e contiene lo stesso `webhook-id` e la stessa firma. Una volta che il tuo server di sicurezza AI ha risposto, lo scambio non viene mai ritentato.

### Fallimenti del webhook

Timeout, stati non-200 (redirect inclusi), corpi di risposta non analizzabili o sovradimensionati ed endpoint irraggiungibili sono tutti fallimenti del webhook. Un fallimento del webhook non diventa mai un deny; invece, l'impostazione di [gestione dei fallimenti](https://platform.claude.com/docs/it/manage-claude/inference-hooks-configuration) della tua organizzazione decide se la richiesta interessata viene bloccata o procede senza ispezione.

### Circuit breaker

Fallimenti del webhook prolungati attribuibili al tuo server di sicurezza AI fanno scattare un "circuit breaker" (interruttore di circuito) che interrompe l'enforcement: Anthropic smette di contattare il tuo server e la gestione dei fallimenti si applica a ogni richiesta.

A partire da 10 minuti dopo l'intervento, Anthropic verifica se il tuo server si è ripristinato: al massimo circa una volta al minuto invia al tuo server la stessa richiesta di test sintetica inviata da **Test connection** (`source.application` è `config-test`), firmata come qualsiasi altra richiesta e priva di contenuti utente. Rispondi normalmente. Un verdetto valido, di autorizzazione o di rifiuto, reimposta il circuit breaker e l'applicazione delle policy riprende; un errore del webhook lascia il circuit breaker scattato e i controlli continuano. Un amministratore può anche reimpostare il circuit breaker in qualsiasi momento, e le modifiche alla configurazione da parte dell'amministratore interrompono i controlli automatici; consulta [Circuit breaker](https://platform.claude.com/docs/it/manage-claude/inference-hooks-configuration#circuit-breaker).

I frame del prompt e i frame della chiamata a strumento condividono un unico circuit breaker, e gli errori su entrambi contribuiscono a farlo scattare.

Ogni scatto viene registrato come attività `inference_hooks_circuit_breaker_tripped` nell'[Activity Feed](https://platform.claude.com/docs/it/manage-claude/compliance-activity-feed), un'attività per scatto. Mentre il breaker è scattato, non vengono registrate attività Inference hooks per singola richiesta, quindi l'attività di scatto è l'unica traccia nel feed della finestra in cui il breaker è rimasto scattato.

### Latenza

L'applicazione delle policy aggiunge il tempo di andata e ritorno del tuo server di sicurezza AI alla "latency" (latenza) di ogni richiesta governata nella tua organizzazione. Con **Validate tool calls** attivo, una risposta che produce un frame della chiamata a strumento attende anche il relativo verdetto, anche in "shadow mode" (modalità ombra). Mantieni il verdetto rapido ed esegui test di carico sul tuo server prima di distribuirlo a un'organizzazione di grandi dimensioni.

### Indirizzi IP di origine

Le richieste al tuo server di sicurezza AI provengono da `160.79.106.0/24`, parte degli [intervalli IP in uscita](https://platform.claude.com/docs/it/api/ip-addresses) pubblicati da Anthropic. Inserisci in allowlist quel blocco, non gli intervalli in entrata nella stessa pagina, che non lo coprono. L'allowlist riduce l'esposizione del tuo server, ma non sostituisce la verifica della firma: il blocco trasporta traffico in uscita di Anthropic oltre agli Inference hooks.

## Compatibilità futura

Il protocollo cresce senza rompere i server scritti correttamente. Il tuo server deve ignorare:

* Campi di primo livello sconosciuti in entrambi i frame.
* Chiavi sconosciute in `metadata`.
* Nuovi valori di `source.application`.
* Nuovi valori di `actor.type`. `actor` è un'unione discriminata su `type`, e `"user"` è l'unico tipo inviato oggi; un tipo futuro garantisce solo che `type` sia presente.
* Blocchi di contenuto con un `type` non riconosciuto.
* Messaggi con un `role` diverso da `user` o `assistant`, che una revisione successiva potrebbe aggiungere.

Non rifiutare mai una richiesta a causa di un tipo di blocco o campo non riconosciuto; leggi i campi che conosci e salta il resto.

Altri tipi di evento hook verranno introdotti in futuro. Un nuovo tipo di evento è un'aggiunta che il tuo server non può gestire saltando un campo: la richiesta necessita comunque di un verdetto. Quando il `type` di primo livello è un valore che non riconosci, restituisci un verdetto allow anziché uno stato di errore; una risposta di errore è un [fallimento del webhook](https://platform.claude.com/docs/it/manage-claude/inference-hooks-endpoint#webhook-failures), e fallimenti prolungati fanno scattare il [circuit breaker](https://platform.claude.com/docs/it/manage-claude/inference-hooks-endpoint#circuit-breaker).

## Progettare la tua integrazione

Un server di sicurezza AI di produzione compie alcune scelte progettuali oltre al protocollo di trasmissione.

**Deduplica su `webhook-id`.** L'header `webhook-id` è univoco per consegna ed è uguale al `request_id` del corpo, e un retry per fallimento di connessione lo riutilizza, quindi funziona come chiave di idempotenza. Se registri i verdetti, usa questo valore come chiave dei record.

**Registra i verdetti e unisci i rifiuti.** Memorizza ogni verdetto che restituisci insieme al suo `reference_id`. Ogni rifiuto viene registrato come attività di conformità `inference_hooks_request_denied` contenente il `reference_id` restituito dal tuo server, così puoi unire i rifiuti nell'[Activity Feed](https://platform.claude.com/docs/it/manage-claude/compliance-activity-feed) ai record corrispondenti nel tuo sistema.

**Archivia con un server che autorizza sempre.** Per acquisire le trascrizioni in tempo reale senza sorvegliarle, restituisci `{"action": "allow"}` incondizionatamente e rendi persistente il frame dopo aver risposto. Questa è un'alternativa push al polling della [Compliance API](https://platform.claude.com/docs/it/manage-claude/compliance-api), e rispondere prima di rendere persistente mantiene il tuo round trip fuori dal percorso critico dell'utente.

**Scrivi `deny_reason` per l'utente finale.** Il testo che restituisci è ciò che l'utente vede quando la sua richiesta viene bloccata, troncato a 500 caratteri. Indica cosa cambiare, ad esempio quale tipo di contenuto rimuovere, anziché emettere un codice di scanner che solo il tuo team può interpretare.

## Passaggi successivi

<CardGroup cols={2}>
  <Card title="Configurare gli Inference hooks" href="https://platform.claude.com/docs/it/manage-claude/inference-hooks-configuration">
    Abilita gli Inference hooks, connetti e testa il tuo endpoint e controlla enforcement, gestione dei fallimenti e rollout.
  </Card>

  <Card title="Panoramica degli Inference hooks" href="https://platform.claude.com/docs/it/manage-claude/inference-hooks">
    Cosa sono gli Inference hooks, come funziona il round trip del verdetto e quando usarli.
  </Card>
</CardGroup>
