Sviluppare un'integrazione Inference hooks
Costruisci il server di sicurezza AI che riceve le richieste Inference hooks firmate, le verifica e restituisce verdetti di autorizzazione o rifiuto.
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. Per capire cosa sono gli Inference hooks e quando usarli, consulta la panoramica degli 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), quindi chiedi al tuo amministratore di impostarlo come endpoint e testare la connessione: il risultato di Test connection riporta il verdetto allow restituito dal tuo server.
# 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()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 possa raggiungerlo: un URL https:// sulla porta 443, su un host pubblicamente instradabile (gli intervalli privati, di loopback e carrier-grade NAT vengono rifiutati al momento della connessione), con un certificato che si convalida rispetto al trust store delle CA pubbliche, che risponda senza redirect. 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 spiega come il tuo amministratore imposta e testa l'URL.
Ogni richiesta contiene questi header fissi, insieme a eventuali header di richiesta personalizzati 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:
| Header | Valore |
|---|---|
Content-Type | application/json |
User-Agent | anthropic-dlp/1 |
Accept-Encoding | identity |
Oggi esiste un solo evento hook: il prompt frame, inviato una volta per ogni richiesta di inferenza governata, prima che l'inferenza inizi. Anthropic trattiene la richiesta 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. Oggi sempre "prompt"; altri tipi di evento verranno introdotti in futuro, quindi gestisci con grazia un valore non riconosciuto (vedi Compatibilità futura). |
request_id | string | Identificatore opaco per singola chiamata di inferenza, per la correlazione. Uguale 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). |
messages | array | La trascrizione della conversazione fino al punto dell'inferenza. Vedi Blocchi di contenuto. |
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:
{
"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_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. |
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 troncamento, quindi una conversazione lunga con allegati di grandi dimensioni produce un corpo della richiesta grande, fino a un limite superiore di 10 MB. Aumenta il limite del corpo del tuo server per accettare quel tetto. 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 un fallimento del webhook, quindi con la gestione dei fallimenti Allow the request un prompt sovradimensionato raggiungerebbe il modello senza ispezione.
Valori di source
source.application è una stringa aperta, non un enum chiuso. I valori noti sono claude-ai e claude-code; i test di connessione usano config-test. Potrebbero comparire nuovi valori e il tuo server non deve rifiutare una richiesta a causa di uno 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.
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:
{
"action": "allow"
}Per rifiutarla:
{
"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à 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 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
actiondiverso daallowodenyviene 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.
Verificare la firma
Le richieste sono firmate secondo la specifica Standard Webhooks, 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, e abilitare gli Inference hooks ne richiede uno, quindi rifiuta qualsiasi richiesta che arrivi non firmata. Un'eccezione: un test di connessione inviato prima del primo salvataggio della tua organizzazione arriva non firmato, perché il segreto di firma non esiste ancora. Accetta richieste non firmate finché il tuo amministratore non conferma che il segreto esiste, poi rifiutale.
Ruotare il segreto è 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 pubblica anche librerie di verifica per la maggior parte dei linguaggi.
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()
)Semantica operativa
Timeout e retry
Il tuo amministratore imposta un timeout del verdetto tra 1 e 10.000 ms (5.000 ms per impostazione predefinita). Il budget copre l'intero scambio: connessione, handshake TLS, richiesta e risposta.
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 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 lo scatto, Anthropic verifica se il tuo server si è ripreso: al massimo circa una volta al minuto, una richiesta, trasportata dal traffico stesso della tua organizzazione, viene consegnata al tuo server per l'ispezione, firmata e strutturata come qualsiasi altra. Rispondi normalmente. Un verdetto valido, allow o deny, reimposta il breaker e l'enforcement riprende. Un fallimento del webhook lascia il breaker scattato e i test continuano. In entrambi i casi, la richiesta di test stessa procede per il suo utente: il suo verdetto non viene applicato e un test fallito non la blocca, nemmeno con Block the request. Un amministratore può anche reimpostare il breaker in qualsiasi momento, e le modifiche di configurazione dell'amministratore interrompono i test automatici; vedi Circuit breaker.
Ogni scatto viene registrato come attività inference_hooks_circuit_breaker_tripped nell'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'enforcement aggiunge il round trip del tuo server di sicurezza AI alla "latency" (latenza) di ogni richiesta governata nella tua organizzazione. Mantieni il verdetto veloce 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 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 nel prompt frame.
- Chiavi sconosciute in
metadata. - Nuovi valori di
source.application. - Nuovi valori di
actor.type.actorè un'unione discriminata sutype, e"user"è l'unico tipo inviato oggi; un tipo futuro garantisce solo chetypesia presente. - Blocchi di contenuto con un
typenon riconosciuto.
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, e fallimenti prolungati fanno scattare il 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 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, 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
Abilita gli Inference hooks, connetti e testa il tuo endpoint e controlla enforcement, gestione dei fallimenti e rollout.
Cosa sono gli Inference hooks, come funziona il round trip del verdetto e quando usarli.
Was this page helpful?