---
title: Eine Inference-hooks-Integration entwickeln
url: https://platform.claude.com/docs/de/manage-claude/inference-hooks-endpoint
description: Erstelle den KI-Sicherheitsserver, der signierte Inference-hooks-Anfragen empfängt, sie verifiziert und Allow- oder Deny-Verdicts zurückgibt.
---

<Note>
  Inference hooks befinden sich in der Beta-Phase und sind für Claude-Enterprise-Organisationen verfügbar. Feldnamen, Anfrageformen und Header können sich während der Beta ändern.
</Note>

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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-configuration). Um zu erfahren, was Inference hooks sind und wann du sie verwenden solltest, siehe die [Übersicht zu Inference hooks](https://platform.claude.com/docs/de/manage-claude/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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-endpoint#receive-a-request)) und lass dann deinen Administrator [ihn als Endpunkt festlegen und die Verbindung testen](https://platform.claude.com/docs/de/manage-claude/inference-hooks-configuration): Das Ergebnis von **Test connection** (Verbindung testen) meldet das Allow-Verdict, das dein Server zurückgegeben hat.

<CodeGroup exclude="shell">
  ```python Python
  # 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()
  ```

  ```typescript TypeScript
  // Ausführen mit: node server.ts
  import { createServer } from "node:http";

  createServer((request, response) => {
    // Lies den Body vollständig aus, bevor du antwortest; Transkripte können mehrere Megabyte groß sein.
    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
  // Ausführen mit: dotnet run server.cs

  var app = WebApplication.Create();

  app.MapPost("/{**path}", async (HttpRequest request) =>
  {
      // Body vollständig auslesen; Transkripte können mehrere Megabyte groß sein.
      await request.Body.CopyToAsync(Stream.Null);
      return Results.Text("""{"action": "allow"}""", "application/json");
  });

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

  ```go Go
  // Ausführen mit: go run server.go
  package main

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

  func main() {
  	http.HandleFunc("POST /", func(writer http.ResponseWriter, request *http.Request) {
  		// Body vollständig lesen, damit die Verbindung wiederverwendet werden kann; Transkripte können mehrere Megabyte groß sein.
  		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
  // Ausführen mit: java VerdictServer.java
  import com.sun.net.httpserver.HttpServer;

  void main() throws IOException {
      HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
      server.createContext("/", exchange -> {
          // Body leeren, ohne ihn zu puffern; Transkripte können mehrere Megabyte groß sein.
          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
  // Ausführen mit: php -S 0.0.0.0:8000 server.php

  // Body vollständig auslesen; Transkripte können mehrere Megabyte groß sein.
  file_get_contents('php://input');

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

  ```ruby Ruby
  # webrick ist in Ruby 3.4 ein reguläres Gem: gem install webrick, oder füge gem "webrick" hinzu.
  # Ausführen mit: 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>
  Diese Server akzeptieren jede Anfrage, auch unsignierte. Füge eine [Signaturprüfung](https://platform.claude.com/docs/de/manage-claude/inference-hooks-endpoint#verify-the-signature) hinzu, bevor du die Durchsetzung aktivierst.
</Note>

## 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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-configuration) beschreibt, wie dein Administrator die URL festlegt und testet.

Jede Anfrage trägt diese festen Header, zusammen mit allen [benutzerdefinierten Anfrage-Headern](https://platform.claude.com/docs/de/manage-claude/inference-hooks-configuration), die dein Administrator konfiguriert hat, und – sobald deine Organisation ein Signing-Secret hat – den `webhook-*`-Signatur-Headern, die in [Die Signatur verifizieren](https://platform.claude.com/docs/de/manage-claude/inference-hooks-endpoint#verify-the-signature) 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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-endpoint#the-tool-call-frame)). Weitere Ereignistypen werden künftig eingeführt, behandle einen unbekannten Wert also robust (siehe [Vorwärtskompatibilität](https://platform.claude.com/docs/de/manage-claude/inference-hooks-endpoint#forward-compatibility)). |
| `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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-endpoint#source-values)).                                                                                                                                                                                                                                                          |
| `messages`   | array            | Das Gesprächstranskript bis zum Zeitpunkt der Inferenz. Siehe [Inhaltsblöcke](https://platform.claude.com/docs/de/manage-claude/inference-hooks-endpoint#content-blocks).                                                                                                                                                                                                                                        |
| `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:

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

### 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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-endpoint#the-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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-configuration) und automatische [Wiederherstellungsprüfungen](https://platform.claude.com/docs/de/manage-claude/inference-hooks-endpoint#circuit-breaker) 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](https://platform.claude.com/docs/de/manage-claude/inference-hooks#availability). 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:

* `type` ist `"tool_call"`.
* `messages` enthält nur die neueste Nachricht, die `assistant`-Nachricht, die Claude gerade erzeugt hat: alle `text`-Blöcke und einen `tool_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 von `messages`, da das Protokoll später frühere Nachrichten davor hinzufügen kann.
* Jeder `tool_use`-Block enthält ein `tool_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:

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

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

### 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:

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

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

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

Um sie abzulehnen:

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

| 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](https://platform.claude.com/docs/de/manage-claude/compliance-activity-feed) `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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-configuration) deiner Organisation. Insbesondere:

* Signalisiere ein Deny nicht mit einem Fehlerstatus. Eine Nicht-200-Antwort ist ein Fehler, kein Deny.
* Jeder `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 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](https://www.standardwebhooks.com/)-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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-configuration), 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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-configuration#rotate-your-signing-secret) 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](https://www.standardwebhooks.com/)-Projekt veröffentlicht außerdem Verifizierungsbibliotheken für die meisten Sprachen.

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

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

  ```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>
      /// Gibt true zurück, wenn der Body von Anthropic für diese Organisation signiert wurde.
      /// Anthropic sendet Header-Namen in Kleinbuchstaben, aber Proxys dürfen
      /// die Schreibweise ändern, daher ohne Beachtung der Groß-/Kleinschreibung abgleichen.
      /// </summary>
      public static bool Verify(string secret, IReadOnlyDictionary<string, string> headers, byte[] body)
      {
          // TryAdd behält den ersten Wert, falls ein Proxy Namen lieferte, die sich nur in der
          // Groß-/Kleinschreibung unterscheiden; der Kopierkonstruktor würde dabei eine Exception werfen.
          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
          }

          // Standard-Base64-Alphabet: Ein URL-sicherer Decoder leitet die falschen Key-Bytes ab.
          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 arbeitet in konstanter Zeit und gibt bei abweichender Länge false zurück.
          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 meldet, ob body von Anthropic für diese Organisation signiert wurde.
  // net/http kanonisiert Header-Namen beim Nachschlagen, daher passen auch anders geschriebene Namen.
  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
  	}

  	// Standard-Base64-Alphabet: Ein URL-sicherer Decoder leitet die falschen Key-Bytes ab.
  	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
          }

          // Standard-Base64-Alphabet: Ein URL-sicherer Decoder leitet die falschen Key-Bytes ab.
          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) {
              // Jede JVM liefert HmacSHA256 mit, daher tritt dies zur Laufzeit nie auf.
              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
      }

      // Standard-Base64-Alphabet: Ein URL-sicherer Decoder leitet die falschen Key-Bytes ab.
      $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 ist in Ruby 3.4 ein gebündeltes Gem: Bundler-verwaltete Apps fügen gem "base64" hinzu.
  require "base64"
  require "openssl"

  TOLERANCE_SECONDS = 300

  # Gibt true zurück, wenn der Body von Anthropic für diese Organisation signiert wurde.
  #
  # Anthropic sendet Header-Namen in Kleinbuchstaben, aber Proxys dürfen
  # die Schreibweise ändern, daher den Lookup auf Kleinbuchstaben normalisieren.
  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

    # Standard-base64-Alphabet: Ein URL-sicherer Decoder leitet falsche Key-Bytes ab.
    begin
      key = Base64.strict_decode64(secret.delete_prefix("whsec_"))
    rescue ArgumentError
      return false # misconfigured secret: reject rather than crash
    end

    # Übergib den Body separat, damit sein Encoding nie dem des Präfixes entsprechen muss.
    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 wirft bei Längenabweichung, daher Längen zuerst prüfen.
      candidate.bytesize == expected.bytesize &&
        OpenSSL.fixed_length_secure_compare(candidate, expected)
    end
  end
  ```
</CodeGroup>

## 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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-configuration) 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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-configuration#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](https://platform.claude.com/docs/de/manage-claude/compliance-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](https://platform.claude.com/docs/de/api/ip-addresses). 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. `actor` ist eine über `type` diskriminierte Union, und `"user"` ist die einzige heute gesendete Art; eine zukünftige Art garantiert nur, dass `type` vorhanden ist.
* Inhaltsblöcke mit einem unbekannten `type`.
* Nachrichten mit einer anderen `role` als `user` oder `assistant`, 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](https://platform.claude.com/docs/de/manage-claude/inference-hooks-endpoint#webhook-failures), und anhaltende Fehler lösen den [Circuit Breaker](https://platform.claude.com/docs/de/manage-claude/inference-hooks-endpoint#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](https://platform.claude.com/docs/de/manage-claude/compliance-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](https://platform.claude.com/docs/de/manage-claude/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

<CardGroup cols={2}>
  <Card title="Inference hooks konfigurieren" href="https://platform.claude.com/docs/de/manage-claude/inference-hooks-configuration">
    Aktiviere Inference hooks, verbinde und teste deinen Endpunkt und steuere Durchsetzung, Fehlerbehandlung und Rollout.
  </Card>

  <Card title="Übersicht zu Inference hooks" href="https://platform.claude.com/docs/de/manage-claude/inference-hooks">
    Was Inference hooks sind, wie der Verdict-Roundtrip funktioniert und wann du sie verwenden solltest.
  </Card>
</CardGroup>
