Eine Inference-Hooks-Integration ist ein KI-Sicherheitsserver: ein HTTPS-Dienst, den Anthropic aufruft. Für jede überwachte Anfrage empfängt dein Server einen signierten POST mit dem Konversationstranskript und antwortet mit einem Allow- oder Deny-Urteil. Diese Seite dokumentiert das Protokoll zum Erstellen dieses Servers: die Anfrage- und Urteilsschemata, die Signaturverifizierung und den operativen Vertrag.
Wie du Inference Hooks aktivierst und auf deinen Endpunkt verweist, erfährst du unter Inference Hooks konfigurieren. Was Inference Hooks sind und wann du sie verwenden solltest, findest du in der Inference-Hooks-Übersicht.
Die kleinste funktionierende Integration ist ein Server, der jede Anfrage liest und sie zulässt. Starte einen der folgenden Server, stelle ihn unter einer öffentlichen https://-URL bereit (zum Beispiel hinter einem TLS-terminierenden Reverse-Proxy oder einem Tunnel), und lass dann deinen Administrator ihn als Endpunkt festlegen und die Verbindung testen: Das Ergebnis von Test connection meldet das Allow-Urteil, das dein Server zurückgegeben hat.
# Ausführen mit: 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):
# Leere den Body; Transkripte können mehrere Megabyte groß sein.
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 sendet einen HTTPS-POST an die URL, die dein Administrator konfiguriert. Die gesamte konfigurierte URL ist der Endpunkt: Es gibt kein festes Pfadsuffix, du kannst also jeden Pfad wählen, der zu deinem Server passt.
Hoste deinen KI-Sicherheitsserver so, dass Anthropic ihn erreichen kann: eine https://-URL auf Port 443, auf einem öffentlich routbaren Host (private, Loopback- und Carrier-Grade-NAT-Bereiche werden beim Verbindungsaufbau abgelehnt), mit einem Zertifikat, das gegen den öffentlichen CA-Trust-Store validiert, und ohne Weiterleitungen antwortet. Die konfigurierte URL muss das endgültige Ziel sein. Inference Hooks konfigurieren beschreibt, wie dein Administrator die URL festlegt und testet.
Jede Anfrage enthält diese festen Header, zusammen mit allen benutzerdefinierten Anfrage-Headern, die dein Administrator konfiguriert hat, und – sobald deine Organisation ein Signing-Secret hat – den webhook-*-Signatur-Headern, die unter Signatur verifizieren beschrieben sind:
| Header | Wert |
|---|---|
Content-Type | application/json |
User-Agent | anthropic-dlp/1 |
Accept-Encoding | identity |
Es gibt heute ein Hook-Event: den Prompt-Frame, der einmal pro überwachter Inferenzanfrage gesendet wird, bevor die Inferenz beginnt. Anthropic hält die Anfrage zurück, bis dein KI-Sicherheitsserver antwortet oder das Urteils-Timeout abläuft.
Der Anfrage-Body ist ein JSON-Objekt mit diesen Feldern:
| Feld | Typ | Beschreibung |
|---|---|---|
type | string | Das Hook-Event. Heute immer "prompt"; andere Event-Typen werden in Zukunft eingeführt, behandle einen unbekannten Wert also tolerant (siehe Vorwärtskompatibilität). |
request_id | string | Opaker Bezeichner pro Inferenzaufruf zur Korrelation. Entspricht dem webhook-id-Header. |
tenant_id | string oder null | Opaker Bezeichner für die Organisation, zu der die Anfrage gehört. |
actor | object | Der Principal, dem die Anfrage zugeordnet wird, diskriminiert über type ("user" ist der einzige heute gesendete Wert): id (ein getaggter Bezeichner, stabil über Anfragen hinweg für dasselbe Konto) und email_address (wenn verfügbar). Sowohl id als auch email_address können null sein. |
source | object | Die ursprüngliche Anwendung: application (siehe Source-Werte). |
messages | array | Das Konversationstranskript bis zum Zeitpunkt der Inferenz. Siehe Content-Blöcke. |
session_id | string oder null | Opaker Konversationsbezeichner, wenn einer existiert. Parse ihn nicht. Für Claude Code ist es ein Best-Effort-Sitzungsbezeichner, der vom Client angegeben wird. |
model | string oder null | Öffentlicher Modellbezeichner für diese Anfrage, wenn verfügbar. |
metadata | object | Reservierte Erweiterungs-Map von String-Keys zu String-Werten, heute leer gesendet. Verlasse dich auf nichts darin und toleriere ihr Fehlen, ihr Vorhandensein und alle Keys, die auftauchen. |
Ein Beispiel-Anfrage-Body:
{
"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": {}
}Jeder Eintrag in messages hat eine role von user oder assistant (Tool-Ergebnisse erscheinen unter der user-Rolle, entsprechend dem Content-Modell der öffentlichen Messages API) und ein content-Array von Blöcken, diskriminiert über type:
Block-type | Felder |
|---|---|
text | text: der Textinhalt. |
tool_use | id: der Bezeichner, auf den das zugehörige Tool-Ergebnis verweist. tool_name: der Name des Tools. input: die Argumente, die das Modell an das Tool übergeben hat. |
tool_result | content: die Ausgabe des Tools als Text, wobei Teile durch Zeilenumbrüche verbunden sind; binäre Teile wie Bilder werden durch Platzhaltermarkierungen ersetzt, und rohe Bytes werden nie gesendet. is_error: ob der Tool-Aufruf fehlgeschlagen ist. tool_name: der Name des Tools, damit eine Richtlinie auf die Tool-Identität abstellen kann, ohne einen früheren Block zu referenzieren. tool_use_id: die id des zugehörigen tool_use-Blocks. |
attachment | file_name: der ursprüngliche Dateiname oder Pfad. media_type: der Medientyp des Anhangs. size_bytes: die Größe der ursprünglichen Datei. text: der Textinhalt des Anhangs, wenn verfügbar, wie extrahierter Dokumenttext, ein Audiotranskript oder Link-Metadaten. Rohe Anhang-Bytes werden nie gesendet. |
Ein Block, dessen type du nicht erkennst, ist eine vorwärtskompatible Ergänzung. Das einzige Feld, das er garantiert, ist type; deine Richtlinie kann alle anderen vorhandenen Felder inspizieren, darf die Anfrage aber nicht wegen eines unbekannten Typs ablehnen.
Das Transkript ist die Konversation, wie der Endnutzer sie sieht, bis zum Zeitpunkt der Inferenz: Transkripttext, Tool-Aufrufe und ihre Ergebnisse, extrahierter Anhangtext und vorherige Turns. Es enthält niemals System-Prompts, Tool-Definitionen, Anthropic-internen Kontext, Claudes verborgenes Reasoning oder rohe Datei-Bytes.
Ein Turn, dessen sämtliche Blöcke ausgeschlossen sind, wird vollständig weggelassen, gehe also nicht von einer strikten Abwechslung zwischen User und Assistant aus.
Transkripte werden ungekürzt gesendet, sodass eine lange Konversation mit großen Anhängen einen großen Anfrage-Body erzeugt, bis zu einer Obergrenze von 10 MB. Erhöhe das Body-Limit deines Servers, um diese Obergrenze zu akzeptieren. Mehrere gängige Standardwerte sind deutlich kleiner, darunter nginx client_max_body_size mit 1 MB und Express express.json() mit 100 kB, und ein abgelehnter Body zählt als Webhook-Fehler, sodass unter der Fehlerbehandlung Allow the request ein übergroßer Prompt das Modell ungeprüft erreichen würde.
source.application ist ein offener String, kein geschlossenes Enum. Bekannte Werte sind claude-ai und claude-code; Verbindungstests verwenden config-test. Neue Werte können auftauchen, und dein Server darf eine Anfrage nicht ablehnen, weil er einen Wert nicht erkennt.
Behandle source.application als beratende Routing-Metadaten, nicht als Vertrauensgrenze: Stütze keine sicherheitskritische Richtlinienentscheidung allein darauf.
Antworte mit HTTP 200 und einem JSON-Urteils-Body für beide Ergebnisse; das Feld action unterscheidet sie. Um die Anfrage zuzulassen:
{
"action": "allow"
}Um sie abzulehnen:
{
"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"
}| Feld | Einschränkungen | Semantik |
|---|---|---|
action | "allow" oder "deny"; erforderlich | allow lässt die Inferenz fortfahren; deny lehnt sie ab. |
deny_reason | string oder null; höchstens 500 Zeichen, längere Werte werden gekürzt | Wird dem Endnutzer angezeigt, wenn action deny ist; bei allow ignoriert. |
reference_id | string oder null; höchstens 50 Zeichen aus [A-Za-z0-9._:/-] | Dein eigener Bezeichner für diese Auswertung. Er wird in der inference_hooks_request_denied-Compliance-Aktivität der Ablehnung erfasst und dem Endnutzer nie angezeigt. Halte ihn opak: kein Anfrageinhalt und keine personenbezogenen Daten. |
Ein Deny wird nie wegen eines Formatierungsproblems verworfen: Ein zu langer deny_reason wird gekürzt, eine fehlerhafte reference_id wird stillschweigend verworfen, und die action wird trotzdem berücksichtigt.
Umgekehrt gilt das nicht. Alles andere als HTTP 200 mit einem parsbaren Urteil ist ein Webhook-Fehler, und die Fehlerbehandlung deiner Organisation greift anstelle eines Urteils. Insbesondere:
action-Wert außer allow oder deny wird als Webhook-Fehler behandelt.Anthropic liest höchstens 64 KiB des Antwort-Bodys, und der Body muss unkomprimiert sein. Weiterleitungen werden nicht verfolgt, und Cookies werden ignoriert. Unbekannte Felder im Urteils-Body werden ignoriert, du kannst also neben den hier dokumentierten Feldern ein umfangreicheres Objekt zurückgeben.
Anfragen werden gemäß der Standard Webhooks-Spezifikation signiert, unter Verwendung von drei Headern. Anthropic sendet die Header-Namen in Kleinbuchstaben, und Proxys dürfen die Groß-/Kleinschreibung ändern, suche sie also ohne Berücksichtigung der Groß-/Kleinschreibung.
| Header | Inhalt |
|---|---|
webhook-id | Eindeutiger Bezeichner für diese Zustellung. Entspricht dem request_id im Body. Verwende ihn als Idempotenzschlüssel und als erste Komponente der signierten Payload. |
webhook-timestamp | Unix-Zeit in Sekunden als Dezimalstring, zum Zeitpunkt der Signierung der Anfrage. Lehne einen Zeitstempel ab, der mehr als fünf Minuten von der Uhr deines Servers abweicht, in beide Richtungen. |
webhook-signature | Ein oder mehrere durch Leerzeichen getrennte v1,<base64>-Werte, jeweils ein HMAC-SHA256 über {webhook-id}.{webhook-timestamp}.{raw body bytes}. Akzeptiere die Anfrage, wenn irgendein Wert mit deinem übereinstimmt, unter Verwendung eines zeitkonstanten Vergleichs. |
Zwei Details verursachen die meisten Verifizierungsfehler:
whsec_-Präfix, kodiert mit dem Standard-Base64-Alphabet (+ und /), ebenso wie die Signatur im Header. Ein URL-sicherer Decoder leitet die falschen Key-Bytes ab, wann immer das Secret + oder / enthält, was meistens der Fall ist.Sobald deine Organisation ein Signing-Secret hat, ist jede Anfrage, die Anthropic sendet, signiert, und das Aktivieren von Inference Hooks erfordert eines, lehne also jede Anfrage ab, die unsigniert ankommt. Eine Ausnahme: Ein Verbindungstest, der vor dem ersten Speichern deiner Organisation gesendet wird, kommt unsigniert an, weil das Signing-Secret noch nicht existiert. Akzeptiere unsignierte Anfragen, bis dein Administrator bestätigt, dass das Secret existiert, und lehne sie danach ab.
Das Rotieren des Secrets ist eine sofortige Umstellung, aber Anfragen, die mit dem vorherigen Secret signiert wurden, können danach noch etwa eine Minute lang eintreffen, plus alles, was bereits unterwegs ist. Lass deinen KI-Sicherheitsserver während der Umstellung Signaturen von beiden Secrets akzeptieren, damit diese Nachzügler nicht abgelehnt werden.
Die folgenden Beispiele sind Server-Implementierungen, daher gibt es keinen Shell-Tab: Ein KI-Sicherheitsserver ist ein langlaufender HTTPS-Dienst und keine einmalige Anfrage. Jedes Beispiel verwendet nur die Standardbibliothek der jeweiligen Sprache; das Standard Webhooks-Projekt veröffentlicht außerdem Verifizierungsbibliotheken für die meisten Sprachen.
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()
)
# Vergleiche Bytes: compare_digest auf str wirft bei Nicht-ASCII-Eingabe.
return any(
hmac.compare_digest(expected, candidate.encode())
for candidate in signatures.split()
)Dein Administrator legt ein Urteils-Timeout zwischen 1 und 10.000 ms fest (standardmäßig 5.000 ms). Das Budget deckt den gesamten Austausch ab: Verbindung, TLS-Handshake, Anfrage und Antwort.
Anthropic wiederholt genau einmal, nach einer Verzögerung von 100 ms, und nur wenn der Verbindungsversuch fehlschlägt. Der Retry teilt sich dasselbe Timeout-Budget und trägt dieselbe webhook-id und dieselbe Signatur. Sobald dein KI-Sicherheitsserver geantwortet hat, wird der Austausch nie wiederholt.
Timeouts, Nicht-200-Status (einschließlich Weiterleitungen), nicht parsbare oder zu große Antwort-Bodys und nicht erreichbare Endpunkte sind alles Webhook-Fehler. Ein Webhook-Fehler wird nie zu einem Deny; stattdessen entscheidet die Fehlerbehandlungs-Einstellung deiner Organisation, ob die betroffene Anfrage blockiert wird oder ohne Inspektion fortfährt.
Anhaltende Webhook-Fehler, die deinem KI-Sicherheitsserver zuzuschreiben sind, lösen einen „circuit breaker" (Sicherungsschalter) aus, der die Durchsetzung stoppt: Anthropic kontaktiert deinen Server nicht mehr, und die Fehlerbehandlung gilt für jede Anfrage. Die Wiederherstellung erfolgt auf der Admin-Seite: Repariere den Server, und lass dann deinen Administrator Enforce verdicts wieder einschalten. Siehe Circuit Breaker.
Die Durchsetzung fügt den Roundtrip deines KI-Sicherheitsservers zur „latency" (Latenz) jeder überwachten Anfrage in deiner Organisation hinzu. Halte das Urteil schnell, und führe Lasttests für deinen Server durch, bevor du ihn in einer großen Organisation ausrollst.
Anfragen an deinen KI-Sicherheitsserver stammen von 160.79.106.0/24, Teil der von Anthropic veröffentlichten ausgehenden IP-Bereiche. Setze diesen Block auf die Allowlist, nicht die eingehenden Bereiche auf derselben Seite, die ihn nicht abdecken. Allowlisting verringert die Angriffsfläche deines Servers, ist aber kein Ersatz für die Signaturverifizierung: Der Block trägt Anthropic-Egress-Traffic über Inference Hooks hinaus.
Das Protokoll wächst, ohne korrekt geschriebene Server zu brechen. Dein Server muss Folgendes ignorieren:
metadata.source.application-Werte.actor.type-Werte. actor ist eine Union, diskriminiert über type, und "user" ist die einzige heute gesendete Art; eine zukünftige Art garantiert nur, dass type vorhanden ist.type.Lehne eine Anfrage nie wegen eines unbekannten Block-Typs oder Felds ab; lies die Felder, die du kennst, und überspringe den Rest.
Andere Hook-Event-Typen werden in Zukunft eingeführt. Ein neuer Event-Typ ist eine Ergänzung, die dein Server nicht durch Überspringen eines Felds handhaben kann: Die Anfrage braucht trotzdem ein Urteil. Wenn das Top-Level-type ein Wert ist, den du nicht erkennst, gib ein Allow-Urteil zurück statt eines Fehlerstatus; eine Fehlerantwort ist ein Webhook-Fehler, und anhaltende Fehler lösen den Circuit Breaker aus.
Ein produktiver KI-Sicherheitsserver trifft einige Designentscheidungen über das Wire-Protokoll hinaus.
Dedupliziere anhand von webhook-id. Der webhook-id-Header ist pro Zustellung eindeutig und entspricht dem request_id im Body, und ein Retry bei Verbindungsfehler verwendet ihn wieder, sodass er als Idempotenzschlüssel funktioniert. Wenn du Urteile aufzeichnest, verwende ihn als Schlüssel für die Datensätze.
Zeichne Urteile auf und verknüpfe Ablehnungen. Speichere jedes Urteil, das du zurückgibst, zusammen mit seiner reference_id. Jede Ablehnung wird als inference_hooks_request_denied-Compliance-Aktivität aufgezeichnet, die die reference_id trägt, die dein Server zurückgegeben hat, sodass du Ablehnungen im Activity Feed mit den passenden Datensätzen in deinem eigenen System verknüpfen kannst.
Archiviere mit einem Always-Allow-Server. Um Transkripte in Echtzeit zu erfassen, ohne sie zu überwachen, gib bedingungslos {"action": "allow"} zurück und persistiere den Frame nach dem Antworten. Dies ist eine Push-basierte Alternative zum Polling der Compliance API, und das Antworten vor dem Persistieren hält deinen Roundtrip aus dem kritischen Pfad des Nutzers heraus.
Schreibe deny_reason für den Endnutzer. Der Text, den du zurückgibst, ist das, was der Nutzer sieht, wenn seine Anfrage blockiert wird, gekürzt auf 500 Zeichen. Sag ihm, was er ändern soll, etwa welche Art von Inhalt er entfernen soll, anstatt einen Scanner-Code auszugeben, den nur dein Team interpretieren kann.
Aktiviere Inference Hooks, verbinde und teste deinen Endpunkt und steuere Durchsetzung, Fehlerbehandlung und Rollout.
Was Inference Hooks sind, wie der Urteils-Roundtrip funktioniert und wann du sie verwenden solltest.
Was this page helpful?