Eine Inference-hooks-Integration entwickeln
Erstelle den KI-Sicherheitsserver, der signierte Inference-hooks-Anfragen empfängt, sie verifiziert und Allow- oder Deny-Verdicts zurückgibt.
Eine Inference-hooks-Integration ist ein „AI security server“ (KI-Sicherheitsserver): ein HTTPS-Dienst, den Anthropic aufruft. Für jede kontrollierte Anfrage erhält dein Server einen signierten POST, der das Gesprächstranskript enthält, und antwortet mit einem „verdict“ (Urteil) – allow oder deny. Diese Seite dokumentiert das Protokoll zum Erstellen dieses Servers: die Anfrage- und Verdict-Schemas, die Signaturprüfung und den operativen Vertrag.
Um Inference hooks einzuschalten und auf deinen Endpunkt zu richten, siehe Inference hooks konfigurieren. Um zu erfahren, was Inference hooks sind und wann du sie verwenden solltest, siehe die Übersicht zu Inference hooks.
Einen ersten Verdict-Roundtrip erreichen
Die kleinste funktionierende Integration ist ein Server, der jede Anfrage liest und sie erlaubt. Führe einen der folgenden Server aus, stelle ihn unter einer öffentlichen https://-URL bereit (zum Beispiel hinter einem TLS-terminierenden Reverse-Proxy auf einem Host, den du kontrollierst, nicht über einen Reverse-Tunnel-Dienst; siehe Eine Anfrage empfangen) und lass dann deinen Administrator ihn als Endpunkt festlegen und die Verbindung testen: Das Ergebnis von Test connection (Verbindung testen) meldet das Allow-Verdict, 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):
# Body vollständig einlesen; 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()Eine Anfrage empfangen
Anthropic sendet einen HTTPS-POST an die URL, die dein Administrator konfiguriert. Die gesamte konfigurierte URL ist der Endpunkt: Es gibt kein festes Pfadsuffix, wähle also einen beliebigen Pfad, der zu deinem Server passt.
Hoste deinen KI-Sicherheitsserver dort, wo Anthropic ihn erreichen kann: unter einer 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 wird, und mit Antworten ohne Weiterleitungen. Der Host muss eine IPv4-Adresse haben, die Anthropic auch dann verwendet, wenn der Host zusätzlich IPv6-Adressen hat; eine URL, deren Host localhost oder eine IPv6-Adresse ist, wird abgelehnt. Die konfigurierte URL muss das endgültige Ziel sein. Reverse-Tunnel-Hosts (ngrok und ähnliche Tunnel-Dienste) werden nicht unterstützt: Die Netzwerkrichtlinie von Anthropic blockiert sie. Hoste deinen Server auf einer Domain, die du kontrollierst. Inference hooks konfigurieren beschreibt, wie dein Administrator die URL festlegt und testet.
Jede Anfrage trägt 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 in Die Signatur verifizieren beschrieben sind:
| Header | Wert |
|---|---|
Content-Type | application/json |
User-Agent | anthropic-dlp/1 |
Accept-Encoding | identity |
Es gibt zwei Hook-Ereignisse, die anhand des Felds type der obersten Ebene unterschieden werden. Der „prompt frame“ (Prompt-Frame) wird einmal pro kontrollierter Inferenzanfrage gesendet, bevor die Inferenz beginnt. Der „tool call frame“ (Tool-Call-Frame) wird gesendet, wenn eine Modellantwort Tool-Aufrufe enthält, bevor einer davon ausgeführt wird, und zwar in Organisationen, in denen Validate tool calls aktiviert ist. In beiden Fällen wartet Anthropic, bis dein KI-Sicherheitsserver antwortet oder das Verdict-Timeout abläuft.
Der Prompt-Frame
Der Anfrage-Body ist ein JSON-Objekt mit diesen Feldern:
| Feld | Typ | Beschreibung |
|---|---|---|
type | string | Das Hook-Ereignis: "prompt" oder "tool_call" (siehe Der Tool-Call-Frame). Weitere Ereignistypen werden künftig eingeführt, behandle einen unbekannten Wert also robust (siehe Vorwärtskompatibilität). |
request_id | string | Opaker Bezeichner pro Frame zur Korrelation. Entspricht dem Header webhook-id. |
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 desselben Kontos hinweg) und email_address (sofern verfügbar). Sowohl id als auch email_address können null sein. |
source | object | Die Ursprungsanwendung: application (siehe Source-Werte). |
messages | array | Das Gesprächstranskript bis zum Zeitpunkt der Inferenz. Siehe Inhaltsblöcke. |
session_id | string oder null | Opaker Gesprächsbezeichner, sofern einer existiert. Parse ihn nicht. Für Claude Code ist es ein vom Client behaupteter Sitzungsbezeichner nach bestem Bemühen. |
model | string oder null | Öffentlicher Modellbezeichner für diese Anfrage, sofern verfügbar. |
metadata | object | Reservierte Erweiterungs-Map von String-Schlüsseln auf String-Werte, heute leer gesendet. Verlange nichts von ihr und toleriere ihr Fehlen, ihr Vorhandensein und alle Schlüssel, die erscheinen. |
Ein Beispiel für einen Anfrage-Body:
{
"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": {}
}Inhaltsblöcke
Jeder Eintrag in messages hat eine role von user oder assistant (Tool-Ergebnisse erscheinen unter der Rolle user, entsprechend dem Inhaltsmodell der öffentlichen Messages API) und ein content-Array von Blöcken, die über type diskriminiert werden:
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_info: nur in einem Tool-Call-Frame (anderswo weggelassen, niemals null), ein Objekt, das angibt, wer das Tool ausführt oder bereitstellt; siehe Der Tool-Call-Frame. |
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 heranzuziehen. 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 Originaldatei. text: der Textinhalt des Anhangs, sofern verfügbar, etwa extrahierter Dokumenttext, ein Audiotranskript oder Link-Metadaten. Rohe Anhang-Bytes werden nie gesendet. |
Abgesehen von type, dem text eines text-Blocks sowie content und is_error eines tool_result-Blocks kann jedes dieser Felder null sein, wenn der Wert nicht bekannt ist; beispielsweise kommt ein Bild als attachment-Block an, bei dem file_name und text auf null gesetzt sind.
Ein Block, dessen type du nicht erkennst, ist eine vorwärtskompatible Ergänzung. Das einzige Feld, das er garantiert, ist type; deine Richtlinie darf alle anderen vorhandenen Felder inspizieren, darf die Anfrage aber nicht wegen eines unbekannten Typs ablehnen.
Was das Transkript enthält
Das Transkript ist das Gespräch, wie der Endnutzer es sieht, bis zum Zeitpunkt der Inferenz: Transkripttext, Tool-Aufrufe und ihre Ergebnisse, extrahierter Anhangstext 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 einem strikten Wechsel zwischen user und assistant aus.
Transkripte werden ungekürzt gesendet, sodass eine lange Konversation mit großen Anhängen einen großen Anfragetext erzeugt. In der Praxis hält das „context window" (Kontextfenster) des Modells die Anfragetexte unter etwa 10 MB, das Protokoll erlaubt jedoch bis zu 64 MiB. 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 Anfragetext zählt als Webhook-Fehler, sodass bei der Fehlerbehandlung Allow the request ein übergroßer Prompt ungeprüft das Modell erreichen würde.
Source-Werte
source.application ist ein offener String, kein geschlossenes Enum. Gängige Werte sind claude-ai, claude-code und cowork; Verbindungstests und automatische Wiederherstellungsprüfungen des Circuit Breakers verwenden config-test. Neue Werte können auftreten, und dein Server darf eine Anfrage nicht wegen eines Werts ablehnen, den er nicht kennt.
Behandle source.application als beratende Routing-Metadaten, nicht als Vertrauensgrenze: Stütze eine sicherheitskritische Richtlinienentscheidung nicht allein darauf.
Der Tool-Call-Frame
Wenn Claude Tool-Aufrufe erzeugt, sendet Anthropic einen Tool-Call-Frame, der sie auflistet. Aufrufe einiger eigener Tools von claude.ai können weggelassen werden, und eine Antwort, deren einzige Tool-Aufrufe weggelassen werden, erzeugt keinen Tool-Call-Frame; siehe Verfügbarkeit. Ein Verdict gilt für den gesamten Frame: Du kannst nicht einige Tool-Aufrufe erlauben und andere ablehnen. Der Frame geht an denselben Endpunkt wie der Prompt-Frame, mit denselben Headern, derselben Signatur und denselben Feldern der obersten Ebene. Tool-Aufrufe aus Code, den Claude im Code-Execution-Tool ausführt, werden auf dieselbe Weise in separaten Tool-Call-Frames gesendet, bevor sie ausgeführt werden.
Er unterscheidet sich in drei Punkten vom Prompt-Frame:
typeist"tool_call".messagesenthält nur die neueste Nachricht, dieassistant-Nachricht, die Claude gerade erzeugt hat: alletext-Blöcke und einentool_use-Block pro Tool-Aufruf, den der Frame auflistet, in der Reihenfolge, in der das Modell sie erzeugt hat. Das frühere Gespräch wird weggelassen, weil der vor diesem Modellaufruf gesendete Prompt-Frame es enthielt. Lies den letzten Eintrag vonmessages, da das Protokoll später frühere Nachrichten davor hinzufügen kann.- Jeder
tool_use-Block enthält eintool_info-Objekt, das angibt, wer das Tool ausführt oder bereitstellt.
Wo session_id gesetzt ist, ist sie in beiden Frames gleich. Der Tool-Call-Frame hat seine eigene request_id, die wie die des Prompt-Frames opak ist.
tool_info gibt an, wer das Tool ausführt oder bereitstellt, nicht, worauf das Tool zugreifen kann. Es ist eine von vier Arten, die anhand des Felds type unterschieden werden, und jede Art hat ihre eigenen Felder. Anthropic sendet die erste der folgenden Arten, die auf das Tool zutrifft. Neue Arten können auftreten: Akzeptiere einen type, den du nicht kennst, und verlasse dich bei einer solchen Art nur auf type.
Ein optionales Feld, das nicht zutrifft, wird weggelassen, niemals null, sodass ein tool_info auch nur {"type": "client"} sein kann. tool_name wird von demjenigen gewählt, der das Tool definiert hat, und der toolset_name eines Servers von demjenigen, der die Anfrage geschrieben hat, behandle also keines von beiden als Vertrauensgrenze.
Plattform-Tools
Ein Plattform-Tool ist eines, das die Claude API selbst ausführt, während sie die Anfrage bedient, etwa Websuche oder Codeausführung.
| Feld | Vorhanden | Beschreibung |
|---|---|---|
type | Immer | "platform" |
tool_type | Immer | Der versionierte Typ des Tools, etwa web_search_20250305. Gleiche ihn exakt ab; parse keinen Namen und kein Datum daraus. |
toolset_name | Optional | Die Gruppe von Tools, zu der es gehört. |
Anwendungs-Tools
Ein Anwendungs-Tool ist eines, das die Anthropic-Anwendung, die die Anfrage stellt, selbst bereitstellt, etwa die eigenen Tools von claude.ai.
| Feld | Vorhanden | Beschreibung |
|---|---|---|
type | Immer | "application" |
toolset_name | Optional | Die Gruppe von Tools, zu der es gehört. |
Ein tool_use-Block für ein Websuche-Tool, das claude.ai selbst bereitstellt:
{
"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 benennt dieses Tool nicht immer web_search, gleiche also nicht allein anhand des Namens ab. input enthält immer die Suchanfrage und kann weitere Felder enthalten.
Drittanbieter-Tools
Ein Drittanbieter-Tool ist eines auf einem Server, den Anthropic kennt, etwa ein claude.ai-Connector oder ein MCP-Server, den die Anfrage in mcp_servers benennt. Das bedeutet nicht, dass Anthropic den Server geprüft hat.
| Feld | Vorhanden | Beschreibung |
|---|---|---|
type | Immer | "third_party" |
toolset_name | Optional | Der Name, den die Anfrage dem Server gibt. |
origin | Optional | Schema, Host und nicht standardmäßiger Port der Server-URL. Es kann eine private oder lokale Adresse sein, einschließlich localhost. Ein fehlendes origin bedeutet unbekannt, nicht sicher. |
verified_origin | Immer, wenn origin vorhanden ist | true nur dann, wenn sich die Server von Anthropic selbst mit dem Server verbinden. Behandle false als einen Ursprung, den Anthropic nicht bestätigt hat. |
Ein tool_use-Block für ein Tool auf einem MCP-Server, den die Anfrage in mcp_servers als crm benennt:
{
"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
}
}Client-Tools
Ein Client-Tool ist jedes andere Tool, in der Regel eines, das die Anwendung ausführt, die Claude aufruft. Ein Tool, das Anthropic definiert, das aber die aufrufende Anwendung ausführt, etwa bash oder Computer Use, ist ebenfalls ein Client-Tool und trägt seinen versionierten Typ in tool_type. Ein Aufruf eines Tools, das die Anfrage nicht deklariert, ist ebenfalls "client".
Ein Tool auf einem MCP-Server, mit dem sich Claude Code direkt vom Computer des Nutzers aus verbindet, etwa ein lokaler MCP-Server, ist ein Client-Tool. Sein tool_info ist {"type": "client"}, und sein tool_name ist der Name, den Claude Code ihm gibt, in der Form mcp__<server>__<tool>.
Ein Deny stoppt den Aufruf, bevor Claude Code ihn ausführt. Der Austausch zwischen Claude Code und einem lokalen Server läuft nicht über Anthropic, sodass dein KI-Sicherheitsserver das Ergebnis des Tools nur sieht, wenn Claude Code es zurücksendet. Sein Text befindet sich dann in einem tool_result-Block im nächsten Prompt-Frame.
| Feld | Vorhanden | Beschreibung |
|---|---|---|
type | Immer | "client" |
tool_type | Optional | Der versionierte Typ des Tools, etwa bash_20250124, wenn Anthropic das Tool definiert. Gleiche ihn exakt ab; parse keinen Namen und kein Datum daraus. |
toolset_name | Optional | Die Gruppe von Tools, zu der es gehört, etwa browser. |
Ein tool_use-Block für ein Tool, das die aufrufende Anwendung deklariert:
{
"type": "tool_use",
"id": "toolu_01AbCdEfGhIjKlMnOpQrStUv",
"tool_name": "read_file",
"input": {
"path": "reports/q3.txt"
},
"tool_info": {
"type": "client"
}
}Ein tool_use-Block für bash, das Anthropic definiert und die aufrufende Anwendung ausführt:
{
"type": "tool_use",
"id": "toolu_01HiJkLmNoPqRsTuVwXyZaBc",
"tool_name": "bash",
"input": {
"command": "ls -la reports/"
},
"tool_info": {
"type": "client",
"tool_type": "bash_20250124"
}
}Ein Verdict zurückgeben
Antworte für beide Ergebnisse mit HTTP 200 und einem JSON-Verdict-Body; das Feld action diskriminiert. Um die Anfrage zu erlauben:
{
"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 Compliance-Aktivität inference_hooks_request_denied der Ablehnung aufgezeichnet und dem Endnutzer nie angezeigt. Halte ihn opak: kein Anfrageinhalt und keine personenbezogenen Daten. |
Ein Deny wird niemals wegen eines Formatierungsproblems verworfen: Ein übergroßer deny_reason wird gekürzt, eine fehlerhaft formatierte reference_id wird stillschweigend verworfen, und die action wird trotzdem berücksichtigt.
Das Umgekehrte gilt nicht. Alles andere als HTTP 200 mit einem parsebaren Verdict ist ein Webhook-Fehler, und anstelle eines Verdicts greift die Fehlerbehandlung deiner Organisation. Insbesondere:
- Signalisiere ein Deny nicht mit einem Fehlerstatus. Eine Nicht-200-Antwort ist ein Fehler, kein Deny.
- Jeder
action-Wert außerallowoderdenywird 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 Verdict-Body werden ignoriert, sodass du neben den hier dokumentierten Feldern ein reichhaltigeres Objekt zurückgeben kannst.
Ein Tool-Call-Frame verwendet denselben Verdict-Body, und jede Regel in diesem Abschnitt gilt unverändert dafür. allow lässt die Tool-Aufrufe ausführen, und die Antwort wird fortgesetzt; deny stoppt alle und beendet die Antwort mit demselben Fehler wie bei einem abgelehnten Prompt, einschließlich deines deny_reason. Text, der vor dem ersten Tool-Aufruf ausgeliefert wurde, wird nicht zurückgenommen.
Die Signatur verifizieren
Anfragen werden gemäß der Standard Webhooks-Spezifikation mit drei Headern signiert. Anthropic sendet die Header-Namen in Kleinbuchstaben, und Proxys dürfen ihre Schreibweise ändern, schlage sie also ohne Berücksichtigung der Groß-/Kleinschreibung nach.
| Header | Inhalt |
|---|---|
webhook-id | Eindeutiger Bezeichner für diese Zustellung. Entspricht der request_id des Bodys. Verwende ihn als Idempotenzschlüssel und als erste Komponente der signierten Payload. |
webhook-timestamp | Unix-Zeit in Sekunden als Dezimalstring, zu der die Anfrage signiert wurde. 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 Vergleichs in konstanter Zeit. |
Zwei Details verursachen die meisten Verifizierungsfehler:
- Verifiziere rohe Bytes. Berechne den HMAC über den Body genau so, wie er empfangen wurde, vor jeglichem JSON-Parsing oder erneuter Kodierung.
- Dekodiere das Secret mit einem Standard-Base64-Decoder. Das Signing-Secret ist der Wert nach dem Präfix
whsec_, kodiert mit dem Standard-Base64-Alphabet (+und/), ebenso wie die Signatur im Header. Ein URL-sicherer Decoder leitet die falschen Schlüssel-Bytes ab, sobald das Secret+oder/enthält, was meistens der Fall ist.
Sobald deine Organisation ein Signatur-Secret hat, wird jede von Anthropic gesendete Anfrage signiert, einschließlich des Verbindungstests, da der Einrichtungsablauf das Secret vor dem ersten Test generiert. Das Aktivieren von Inference hooks erfordert ein Secret, lehne also jede unsigniert eintreffende Anfrage ab. Eine Ausnahme: Eine Organisation, die Inference hooks aktiviert hat, bevor das Secret erforderlich war, sendet weiterhin unsignierte Anfragen, bis ihr Administrator eines generiert. Akzeptiere unsignierte Anfragen nur, 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 noch etwa eine Minute danach eintreffen, zuzüglich allem, was bereits unterwegs ist. Lass deinen KI-Sicherheitsserver während der Umstellung Signaturen beider 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 lang laufender HTTPS-Dienst und keine einmalige Anfrage. Jedes Beispiel verwendet nur die Standardbibliothek der 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()
)
# Bytes vergleichen: compare_digest auf str löst bei Nicht-ASCII-Eingabe einen Fehler aus.
return any(
hmac.compare_digest(expected, candidate.encode())
for candidate in signatures.split()
)Operative Semantik
Timeout und Wiederholung
Dein Administrator legt ein Verdict-Timeout zwischen 1 und 10.000 ms fest (standardmäßig 5.000 ms). Das Budget umfasst den gesamten Austausch: Verbindung, TLS-Handshake, Anfrage und Antwort. Dasselbe Timeout gilt für Tool-Call-Frames.
Anthropic wiederholt genau einmal, nach einer Verzögerung von 100 ms, und nur, wenn der Verbindungsversuch fehlschlägt. Die Wiederholung teilt dasselbe Timeout-Budget und trägt dieselbe webhook-id und dieselbe Signatur. Sobald dein KI-Sicherheitsserver geantwortet hat, wird der Austausch nie wiederholt.
Webhook-Fehler
Timeouts, Nicht-200-Statuscodes (einschließlich Weiterleitungen), nicht parsebare oder übergroße Antwort-Bodys und nicht erreichbare Endpunkte sind alle Webhook-Fehler. Ein Webhook-Fehler wird niemals zu einem Deny; stattdessen entscheidet die Einstellung zur Fehlerbehandlung deiner Organisation, ob die betroffene Anfrage blockiert wird oder ohne Prüfung fortfährt.
Circuit Breaker
Anhaltende Webhook-Fehler, die deinem KI-Sicherheitsserver zuzuschreiben sind, lösen einen „circuit breaker“ (Schutzschalter) aus, der die Durchsetzung stoppt: Anthropic kontaktiert deinen Server nicht mehr, und die Fehlerbehandlung gilt für jede Anfrage.
Ab 10 Minuten nach dem Auslösen prüft Anthropic, ob sich dein Server erholt hat: Höchstens etwa einmal pro Minute sendet es deinem Server dieselbe synthetische Testanfrage, die auch Test connection sendet (source.application ist config-test), signiert wie jede andere Anfrage und ohne Benutzerinhalte. Beantworte sie ganz normal. Eine gültige Entscheidung, ob allow oder deny, setzt den Circuit Breaker zurück, und die Durchsetzung wird fortgesetzt; ein Webhook-Fehler lässt den Circuit Breaker ausgelöst, und die Prüfungen gehen weiter. Ein Administrator kann den Circuit Breaker außerdem jederzeit zurücksetzen, und Konfigurationsänderungen durch Administratoren beenden die automatischen Prüfungen; siehe Circuit Breaker.
Prompt-Frames und Tool-Call-Frames teilen sich einen Circuit Breaker, und Fehler bei beiden zählen dafür.
Jedes Auslösen wird als inference_hooks_circuit_breaker_tripped-Aktivität im Activity Feed aufgezeichnet, eine Aktivität pro Auslösen. Während der Circuit Breaker ausgelöst ist, werden keine Inference-hooks-Aktivitäten pro Anfrage aufgezeichnet, sodass die Auslöse-Aktivität der einzige Eintrag des Feeds über das ausgelöste Zeitfenster ist.
Latenz
Die Durchsetzung fügt den Roundtrip deines KI-Sicherheitsservers zur „latency" (Latenz) jeder kontrollierten Anfrage in deiner Organisation hinzu. Wenn Validate tool calls aktiviert ist, wartet eine Antwort, die einen Tool-Call-Frame erzeugt, ebenfalls auf dessen Verdict, auch im Shadow-Modus. Halte das Verdict schnell und führe Lasttests für deinen Server durch, bevor du ihn in einer großen Organisation ausrollst.
Quell-IP-Adressen
Anfragen an deinen KI-Sicherheitsserver stammen aus 160.79.106.0/24, einem Teil von Anthropics veröffentlichten ausgehenden IP-Bereichen. 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 Signaturprüfung: Der Block trägt ausgehenden Anthropic-Traffic über Inference hooks hinaus.
Vorwärtskompatibilität
Das Protokoll wächst, ohne korrekt geschriebene Server zu brechen. Dein Server muss Folgendes ignorieren:
- Unbekannte Felder der obersten Ebene in beiden Frames.
- Unbekannte Schlüssel in
metadata. - Neue
source.application-Werte. - Neue
actor.type-Werte.actorist eine übertypediskriminierte Union, und"user"ist die einzige heute gesendete Art; eine zukünftige Art garantiert nur, dasstypevorhanden ist. - Inhaltsblöcke mit einem unbekannten
type. - Nachrichten mit einer anderen
rolealsuseroderassistant, die eine spätere Revision hinzufügen kann.
Lehne niemals eine Anfrage wegen eines unbekannten Blocktyps oder Felds ab; lies die Felder, die du kennst, und überspringe den Rest.
Weitere Hook-Ereignistypen werden in Zukunft eingeführt. Ein neuer Ereignistyp ist eine Ergänzung, die dein Server nicht durch Überspringen eines Felds behandeln kann: Die Anfrage benötigt trotzdem ein Verdict. Wenn der type auf oberster Ebene ein Wert ist, den du nicht erkennst, gib ein Allow-Verdict statt eines Fehlerstatus zurück; eine Fehlerantwort ist ein Webhook-Fehler, und anhaltende Fehler lösen den Circuit Breaker aus.
Deine Integration entwerfen
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 der request_id des Bodys, und eine Wiederholung nach Verbindungsfehler verwendet ihn erneut, sodass er als Idempotenzschlüssel funktioniert. Wenn du Verdicts aufzeichnest, verwende ihn als Schlüssel für die Datensätze.
Zeichne Verdicts auf und verknüpfe Ablehnungen. Speichere jedes Verdict, das du zurückgibst, zusammen mit seiner reference_id. Jede Ablehnung wird als Compliance-Aktivität inference_hooks_request_denied aufgezeichnet, die die von deinem Server zurückgegebene reference_id trägt, 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 kontrollieren, 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. Sage ihm, was er ändern soll, etwa welche Art von Inhalt er entfernen soll, anstatt einen Scanner-Code auszugeben, den nur dein Team interpretieren kann.
Nächste Schritte
Aktiviere Inference hooks, verbinde und teste deinen Endpunkt und steuere Durchsetzung, Fehlerbehandlung und Rollout.
Was Inference hooks sind, wie der Verdict-Roundtrip funktioniert und wann du sie verwenden solltest.
Was this page helpful?