Une intégration Inference hooks est un serveur de sécurité IA : un service HTTPS qu'Anthropic appelle. Pour chaque requête gouvernée, votre serveur reçoit un POST signé contenant la transcription de la conversation et répond avec un verdict d'autorisation ou de refus. Cette page documente le protocole pour construire ce serveur : les schémas de requête et de verdict, la vérification de signature et le contrat opérationnel.
Pour activer les Inference hooks et les pointer vers votre point de terminaison, consultez Configurer les Inference hooks. Pour savoir ce que sont les Inference hooks et quand les utiliser, consultez la présentation des Inference hooks.
L'intégration fonctionnelle la plus simple est un serveur qui lit chaque requête et l'autorise. Exécutez l'un des serveurs suivants, exposez-le à une URL publique https:// (par exemple, derrière un proxy inverse terminant TLS ou un tunnel), puis demandez à votre administrateur de le définir comme point de terminaison et de tester la connexion : le résultat de Test connection indique le verdict d'autorisation que votre serveur a renvoyé.
# Exécutez avec : 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):
# Videz le corps ; les transcriptions peuvent atteindre plusieurs mégaoctets.
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()Anthropic envoie un POST HTTPS à l'URL que votre administrateur configure. L'URL configurée entière constitue le point de terminaison : il n'y a pas de suffixe de chemin fixe, vous pouvez donc choisir n'importe quel chemin qui convient à votre serveur.
Hébergez votre serveur de sécurité IA là où Anthropic peut l'atteindre : une URL https:// sur le port 443, sur un hôte routable publiquement (les plages privées, de bouclage et de NAT de classe opérateur sont refusées au moment de la connexion), avec un certificat validé par le magasin de confiance des autorités de certification publiques, répondant sans redirections. L'URL configurée doit être la destination finale. Configurer les Inference hooks explique comment votre administrateur définit et teste l'URL.
Chaque requête comporte ces en-têtes fixes, ainsi que tous les en-têtes de requête personnalisés que votre administrateur a configurés et, une fois que votre organisation dispose d'un secret de signature, les en-têtes de signature webhook-* décrits dans Vérifier la signature :
| En-tête | Valeur |
|---|---|
Content-Type | application/json |
User-Agent | anthropic-dlp/1 |
Accept-Encoding | identity |
Il existe aujourd'hui un seul événement de hook : le « prompt frame » (trame de prompt), envoyé une fois par requête d'inférence gouvernée, avant le début de l'inférence. Anthropic retient la requête jusqu'à ce que votre serveur de sécurité IA réponde ou que le délai d'attente du verdict expire.
Le corps de la requête est un objet JSON avec les champs suivants :
| Champ | Type | Description |
|---|---|---|
type | string | L'événement de hook. Toujours "prompt" aujourd'hui ; d'autres types d'événements seront introduits à l'avenir, donc gérez une valeur non reconnue avec souplesse (voir Compatibilité ascendante). |
request_id | string | Identifiant opaque par appel d'inférence pour la corrélation. Égal à l'en-tête webhook-id. |
tenant_id | string ou null | Identifiant opaque de l'organisation à laquelle appartient la requête. |
actor | object | Le principal auquel la requête est attribuée, discriminé par type ("user" est la seule valeur envoyée aujourd'hui) : id (un identifiant étiqueté, stable d'une requête à l'autre pour le même compte) et email_address (lorsque disponible). id et email_address peuvent tous deux être null. |
source | object | L'application d'origine : application (voir Valeurs de source). |
messages | array | La transcription de la conversation jusqu'au point d'inférence. Voir Blocs de contenu. |
session_id | string ou null | Identifiant opaque de conversation, lorsqu'il en existe un. Ne l'analysez pas. Pour Claude Code, il s'agit d'un identifiant de session déclaré par le client, fourni au mieux. |
model | string ou null | Identifiant public du modèle pour cette requête, lorsque disponible. |
metadata | object | Table d'extension réservée de clés string vers des valeurs string, envoyée vide aujourd'hui. N'en exigez rien, et tolérez son absence, sa présence et toutes les clés qui apparaissent. |
Un exemple de corps de requête :
{
"type": "prompt",
"request_id": "req_abc123",
"tenant_id": "11111111-1111-1111-1111-111111111111",
"actor": {
"type": "user",
"id": "user_01AbCdEfGhIjKlMnOpQrStUv",
"email_address": "[email protected]"
},
"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": {}
}Chaque entrée dans messages a un role de user ou assistant (les résultats d'outils apparaissent sous le rôle user, conformément au modèle de contenu de l'API Messages publique) et un tableau content de blocs discriminés par type :
type de bloc | Champs |
|---|---|
text | text : le contenu textuel. |
tool_use | id : l'identifiant auquel le résultat d'outil correspondant fait référence. tool_name : le nom de l'outil. input : les arguments que le modèle a passés à l'outil. |
tool_result | content : la sortie de l'outil sous forme de texte, avec les parties jointes par des sauts de ligne ; les parties binaires telles que les images sont remplacées par des marqueurs de substitution, et les octets bruts ne sont jamais envoyés. is_error : indique si l'appel d'outil a échoué. tool_name : le nom de l'outil, afin qu'une politique puisse conditionner sur l'identité de l'outil sans croiser avec un bloc antérieur. tool_use_id : l'id du bloc tool_use correspondant. |
attachment | file_name : le nom ou chemin du fichier d'origine. media_type : le type de média de la pièce jointe. size_bytes : la taille du fichier d'origine. text : le contenu textuel de la pièce jointe lorsque disponible, tel que le texte extrait d'un document, une transcription audio ou des métadonnées de lien. Les octets bruts de la pièce jointe ne sont jamais envoyés. |
Un bloc dont vous ne reconnaissez pas le type est un ajout compatible avec les versions futures. Le seul champ qu'il garantit est type ; votre politique peut inspecter tous les autres champs présents, mais ne doit pas rejeter la requête à cause d'un type non reconnu.
La transcription est la conversation telle que l'utilisateur final la voit, jusqu'au point d'inférence : texte de la transcription, appels d'outils et leurs résultats, texte extrait des pièces jointes et tours précédents. Elle n'inclut jamais les invites système, les définitions d'outils, le contexte interne à Anthropic, le raisonnement caché de Claude ni les octets bruts des fichiers.
Un tour dont tous les blocs sont exclus est entièrement omis, donc ne supposez pas une alternance stricte entre utilisateur et assistant.
Les transcriptions sont envoyées sans troncature, donc une longue conversation avec de grandes pièces jointes produit un corps de requête volumineux, jusqu'à une limite supérieure de 10 Mo. Augmentez la limite de corps de votre serveur pour accepter ce plafond. Plusieurs valeurs par défaut courantes sont bien plus petites, notamment client_max_body_size de nginx à 1 Mo et express.json() d'Express à 100 ko, et un corps rejeté compte comme un échec de webhook, donc avec la gestion d'échec Allow the request, un prompt surdimensionné atteindrait le modèle sans inspection.
source.application est une chaîne ouverte, pas une énumération fermée. Les valeurs connues sont claude-ai et claude-code ; les tests de connexion utilisent config-test. De nouvelles valeurs peuvent apparaître, et votre serveur ne doit pas rejeter une requête à cause d'une valeur qu'il ne reconnaît pas.
Traitez source.application comme des métadonnées de routage indicatives, pas comme une frontière de confiance : ne fondez pas une décision de politique critique pour la sécurité uniquement sur cette valeur.
Répondez avec HTTP 200 et un corps de verdict JSON pour les deux résultats ; le champ action discrimine. Pour autoriser la requête :
{
"action": "allow"
}Pour la refuser :
{
"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"
}| Champ | Contraintes | Sémantique |
|---|---|---|
action | "allow" ou "deny" ; obligatoire | allow laisse l'inférence se poursuivre ; deny la rejette. |
deny_reason | string ou null ; au plus 500 caractères, les valeurs plus longues sont tronquées | Affiché à l'utilisateur final lorsque action est deny ; ignoré sur allow. |
reference_id | string ou null ; au plus 50 caractères parmi [A-Za-z0-9._:/-] | Votre propre identifiant pour cette évaluation. Il est enregistré sur l'activité de conformité inference_hooks_request_denied du refus et n'est jamais affiché à l'utilisateur final. Gardez-le opaque : pas de contenu de requête ni de données personnelles. |
Un refus n'est jamais écarté à cause d'un problème de formatage : un deny_reason surdimensionné est tronqué, un reference_id mal formé est silencieusement supprimé, et l'action est toujours honorée.
L'inverse n'est pas vrai. Tout ce qui n'est pas HTTP 200 avec un verdict analysable est un échec de webhook, et la gestion des échecs de votre organisation s'applique à la place d'un verdict. En particulier :
action autre que allow ou deny est traitée comme un échec de webhook.Anthropic lit au plus 64 Kio du corps de la réponse, et le corps doit être non compressé. Les redirections ne sont pas suivies, et les cookies sont ignorés. Les champs inconnus dans le corps du verdict sont ignorés, vous pouvez donc renvoyer un objet plus riche en plus des champs documentés ici.
Les requêtes sont signées selon la spécification Standard Webhooks, en utilisant trois en-têtes. Anthropic envoie les noms d'en-têtes en minuscules, et les proxys sont libres de modifier leur casse, donc recherchez-les de manière insensible à la casse.
| En-tête | Contenu |
|---|---|
webhook-id | Identifiant unique pour cette livraison. Égal au request_id du corps. Utilisez-le comme clé d'idempotence et comme premier composant de la charge utile signée. |
webhook-timestamp | Temps Unix en secondes, sous forme de chaîne décimale, au moment où la requête a été signée. Rejetez un horodatage s'écartant de plus de cinq minutes de l'horloge de votre serveur, dans l'une ou l'autre direction. |
webhook-signature | Une ou plusieurs valeurs v1,<base64> séparées par des espaces, chacune étant un HMAC-SHA256 sur {webhook-id}.{webhook-timestamp}.{octets bruts du corps}. Acceptez la requête si l'une des valeurs correspond à la vôtre, en utilisant une comparaison à temps constant. |
Deux détails causent la plupart des bugs de vérification :
whsec_, encodée avec l'alphabet base64 standard (+ et /), tout comme la signature dans l'en-tête. Un décodeur URL-safe dérive les mauvais octets de clé dès que le secret contient + ou /, ce qui est le cas la plupart du temps.Une fois que votre organisation dispose d'un secret de signature, chaque requête qu'Anthropic envoie est signée, et l'activation des Inference hooks en nécessite un, donc rejetez toute requête qui arrive non signée. Une exception : un test de connexion envoyé avant le premier enregistrement de votre organisation arrive non signé, car le secret de signature n'existe pas encore. Acceptez les requêtes non signées jusqu'à ce que votre administrateur confirme que le secret existe, puis rejetez-les.
La rotation du secret est un basculement immédiat, mais des requêtes signées avec le secret précédent peuvent encore arriver pendant environ une minute après, plus tout ce qui est déjà en transit. Faites en sorte que votre serveur de sécurité IA accepte les signatures des deux secrets pendant le basculement afin que ces requêtes retardataires ne soient pas rejetées.
Les exemples suivants sont des implémentations de serveur, il n'y a donc pas d'onglet shell : un serveur de sécurité IA est un service HTTPS de longue durée plutôt qu'une requête ponctuelle. Chaque exemple utilise uniquement la bibliothèque standard du langage ; le projet Standard Webhooks publie également des bibliothèques de vérification pour la plupart des langages.
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()
)
# Comparer les octets : compare_digest sur str lève une erreur avec une entrée non-ASCII.
return any(
hmac.compare_digest(expected, candidate.encode())
for candidate in signatures.split()
)Votre administrateur définit un délai d'attente de verdict entre 1 et 10 000 ms (5 000 ms par défaut). Le budget couvre l'échange entier : connexion, négociation TLS, requête et réponse.
Anthropic réessaie exactement une fois, après un délai de 100 ms, et uniquement lorsque la tentative de connexion échoue. La nouvelle tentative partage le même budget de délai d'attente et porte le même webhook-id et la même signature. Une fois que votre serveur de sécurité IA a répondu, l'échange n'est jamais retenté.
Les dépassements de délai, les statuts non-200 (redirections incluses), les corps de réponse non analysables ou surdimensionnés et les points de terminaison inaccessibles sont tous des échecs de webhook. Un échec de webhook ne devient jamais un refus ; à la place, le paramètre de gestion des échecs de votre organisation décide si la requête concernée est bloquée ou se poursuit sans inspection.
Des échecs de webhook soutenus attribuables à votre serveur de sécurité IA déclenchent un « circuit breaker » (disjoncteur) qui arrête l'application des verdicts : Anthropic cesse de contacter votre serveur, et la gestion des échecs s'applique à chaque requête. La récupération se fait côté administrateur : corrigez le serveur, puis demandez à votre administrateur de réactiver Enforce verdicts. Voir Disjoncteur.
L'application des verdicts ajoute l'aller-retour de votre serveur de sécurité IA à la « latency » (latence) de chaque requête gouvernée dans votre organisation. Gardez le verdict rapide, et effectuez des tests de charge sur votre serveur avant de le déployer dans une grande organisation.
Les requêtes vers votre serveur de sécurité IA proviennent de 160.79.106.0/24, qui fait partie des plages IP sortantes publiées par Anthropic. Mettez ce bloc en liste d'autorisation, pas les plages entrantes de la même page, qui ne le couvrent pas. La mise en liste d'autorisation réduit l'exposition de votre serveur, mais ne remplace pas la vérification de signature : le bloc transporte du trafic sortant d'Anthropic au-delà des Inference hooks.
Le protocole évolue sans casser les serveurs correctement écrits. Votre serveur doit ignorer :
metadata.source.application.actor.type. actor est une union discriminée par type, et "user" est le seul type envoyé aujourd'hui ; un type futur garantit uniquement que type est présent.type non reconnu.Ne rejetez jamais une requête à cause d'un type de bloc ou d'un champ non reconnu ; lisez les champs que vous connaissez et ignorez le reste.
D'autres types d'événements de hook seront introduits à l'avenir. Un nouveau type d'événement est un ajout que votre serveur ne peut pas gérer en ignorant un champ : la requête nécessite toujours un verdict. Lorsque le type de niveau supérieur est une valeur que vous ne reconnaissez pas, renvoyez un verdict d'autorisation plutôt qu'un statut d'erreur ; une réponse d'erreur est un échec de webhook, et des échecs soutenus déclenchent le disjoncteur.
Un serveur de sécurité IA de production fait quelques choix de conception au-delà du protocole réseau.
Dédupliquez sur webhook-id. L'en-tête webhook-id est unique par livraison et égal au request_id du corps, et une nouvelle tentative après échec de connexion le réutilise, donc il fonctionne comme clé d'idempotence. Si vous enregistrez les verdicts, indexez les enregistrements sur cette clé.
Enregistrez les verdicts et joignez les refus. Stockez chaque verdict que vous renvoyez avec son reference_id. Chaque refus est enregistré comme une activité de conformité inference_hooks_request_denied portant le reference_id que votre serveur a renvoyé, vous pouvez donc joindre les refus dans le flux d'activité aux enregistrements correspondants dans votre propre système.
Archivez avec un serveur qui autorise toujours. Pour capturer les transcriptions en temps réel sans les contrôler, renvoyez {"action": "allow"} inconditionnellement et persistez la trame après avoir répondu. C'est une alternative basée sur le push à l'interrogation de l'API de conformité, et répondre avant de persister garde votre aller-retour hors du chemin critique de l'utilisateur.
Rédigez deny_reason pour l'utilisateur final. Le texte que vous renvoyez est ce que l'utilisateur voit lorsque sa requête est bloquée, tronqué à 500 caractères. Dites-lui ce qu'il doit changer, par exemple quel type de contenu supprimer, plutôt que d'émettre un code de scanner que seule votre équipe peut interpréter.
Activez les Inference hooks, connectez et testez votre point de terminaison, et contrôlez l'application des verdicts, la gestion des échecs et le déploiement.
Ce que sont les Inference hooks, comment fonctionne l'aller-retour de verdict et quand les utiliser.
Was this page helpful?