Claude Platform Docs
АдминистрированиеХуки инференса

Разработка интеграции Inference hooks

Создайте сервер безопасности ИИ, который принимает подписанные запросы Inference hooks, проверяет их и возвращает вердикты allow или deny.

Интеграция Inference hooks — это «AI security server» (сервер безопасности ИИ): HTTPS-сервис, который вызывает Anthropic. Для каждого контролируемого запроса ваш сервер получает подписанный POST, содержащий транскрипт разговора, и отвечает вердиктом allow (разрешить) или deny (отклонить). На этой странице описан протокол для создания такого сервера: схемы запроса и вердикта, проверка подписи и операционный контракт.

Чтобы включить Inference hooks и направить их на вашу конечную точку, см. Настройка Inference hooks. Чтобы узнать, что такое Inference hooks и когда их использовать, см. обзор Inference hooks.

Получите первый полный цикл вердикта

Минимальная работающая интеграция — это сервер, который читает каждый запрос и разрешает его. Запустите один из следующих серверов, опубликуйте его по общедоступному URL https:// (например, за обратным прокси с терминацией TLS на хосте, который вы контролируете, а не через сервис обратного туннелирования; см. Получение запроса), затем попросите вашего администратора указать его в качестве конечной точки и протестировать соединение: результат Test connection сообщает вердикт allow, который вернул ваш сервер.

# Запуск: 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):
        # Вычитываем тело полностью; транскрипты могут занимать мегабайты.
        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 отправляет HTTPS POST на URL, который настраивает ваш администратор. Весь настроенный URL является конечной точкой: фиксированного суффикса пути нет, поэтому выберите любой путь, подходящий вашему серверу.

Разместите ваш сервер безопасности ИИ там, где Anthropic сможет до него достучаться: URL https:// на порту 443, на публично маршрутизируемом хосте (частные диапазоны, loopback и диапазоны carrier-grade NAT отклоняются на этапе подключения), с сертификатом, который проходит проверку по публичному хранилищу доверенных центров сертификации, с ответом без перенаправлений. Настроенный URL должен быть конечным пунктом назначения. Хосты обратного туннелирования (ngrok и аналогичные туннельные сервисы) не поддерживаются: сетевая политика Anthropic блокирует их. Размещайте сервер на домене, который вы контролируете. В разделе Настройка Inference hooks описано, как ваш администратор задаёт и тестирует URL.

Каждый запрос содержит эти фиксированные заголовки, а также любые пользовательские заголовки запроса, настроенные вашим администратором, и — как только у вашей организации появится секрет подписи — заголовки подписи webhook-*, описанные в разделе Проверка подписи:

ЗаголовокЗначение
Content-Typeapplication/json
User-Agentanthropic-dlp/1
Accept-Encodingidentity

На сегодня существует одно событие хука: «prompt frame» (кадр подсказки), отправляемый один раз на каждый контролируемый запрос инференса, до начала инференса. Anthropic удерживает запрос, пока ваш сервер безопасности ИИ не ответит или пока не истечёт тайм-аут вердикта.

Кадр подсказки

Тело запроса — это JSON-объект со следующими полями:

ПолеТипОписание
typestringСобытие хука. Сегодня всегда "prompt"; в будущем будут введены другие типы событий, поэтому корректно обрабатывайте нераспознанное значение (см. Прямая совместимость).
request_idstringНепрозрачный идентификатор отдельного вызова инференса для корреляции. Равен заголовку webhook-id.
tenant_idstring или nullНепрозрачный идентификатор организации, к которой относится запрос.
actorobjectСубъект, которому приписывается запрос, различаемый по type ("user" — единственное значение, отправляемое сегодня): id (тегированный идентификатор, стабильный между запросами для одной и той же учётной записи) и email_address (при наличии). И id, и email_address могут быть null.
sourceobjectИсходное приложение: application (см. Значения source).
messagesarrayТранскрипт разговора до момента инференса. См. Блоки содержимого.
session_idstring или nullНепрозрачный идентификатор разговора, если он существует. Не разбирайте его. Для Claude Code это идентификатор сессии, заявленный клиентом по принципу best-effort.
modelstring или nullПубличный идентификатор модели для этого запроса, при наличии.
metadataobjectЗарезервированная карта расширений со строковыми ключами и строковыми значениями, сегодня отправляется пустой. Ничего от неё не требуйте и допускайте её отсутствие, её наличие и любые появляющиеся ключи.

Пример тела запроса:

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

Блоки содержимого

Каждая запись в messages имеет role со значением user или assistant (результаты инструментов появляются под ролью user, в соответствии с моделью содержимого публичного Messages API) и массив content из блоков, различаемых по type:

type блокаПоля
texttext: текстовое содержимое.
tool_useid: идентификатор, на который ссылается соответствующий результат инструмента. tool_name: имя инструмента. input: аргументы, которые модель передала инструменту.
tool_resultcontent: вывод инструмента в виде текста, части соединены переводами строк; бинарные части, такие как изображения, заменяются маркерами-заполнителями, а сырые байты никогда не отправляются. is_error: завершился ли вызов инструмента ошибкой. tool_name: имя инструмента, чтобы политика могла учитывать идентичность инструмента без перекрёстных ссылок на более ранний блок. tool_use_id: id соответствующего блока tool_use.
attachmentfile_name: исходное имя файла или путь. media_type: медиатип вложения. size_bytes: размер исходного файла. text: текстовое содержимое вложения при наличии, например извлечённый текст документа, транскрипт аудио или метаданные ссылки. Сырые байты вложений никогда не отправляются.

Блок, type которого вы не распознаёте, является прямо совместимым дополнением. Единственное поле, которое он гарантирует, — это type; ваша политика может проверять любые другие присутствующие поля, но не должна отклонять запрос из-за нераспознанного типа.

Что содержит транскрипт

Транскрипт — это разговор в том виде, в каком его видит конечный пользователь, до момента инференса: текст транскрипта, вызовы инструментов и их результаты, извлечённый текст вложений и предыдущие ходы. Он никогда не включает системные подсказки, определения инструментов, внутренний контекст Anthropic, скрытые рассуждения Claude или сырые байты файлов.

Ход, каждый блок которого исключён, опускается полностью, поэтому не предполагайте строгого чередования user и assistant.

Транскрипты отправляются без усечения, поэтому длинный разговор с большими вложениями порождает большое тело запроса, вплоть до верхней границы в 10 МБ. Увеличьте лимит тела на вашем сервере, чтобы принимать этот потолок. Некоторые распространённые значения по умолчанию значительно меньше, включая nginx client_max_body_size в 1 МБ и Express express.json() в 100 кБ, а отклонённое тело считается сбоем вебхука, поэтому при обработке сбоев Allow the request подсказка чрезмерного размера достигла бы модели без проверки.

Значения source

source.application — это открытая строка, а не закрытое перечисление. Известные значения — claude-ai и claude-code; тесты соединения используют config-test. Могут появляться новые значения, и ваш сервер не должен отклонять запрос из-за значения, которое он не распознаёт.

Рассматривайте source.application как рекомендательные метаданные маршрутизации, а не как границу доверия: не основывайте критичное для безопасности решение политики только на нём.

Возврат вердикта

Отвечайте HTTP 200 и JSON-телом вердикта для обоих исходов; различает их поле action. Чтобы разрешить запрос:

{
  "action": "allow"
}

Чтобы отклонить его:

{
  "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"
}
ПолеОграниченияСемантика
action"allow" или "deny"; обязательноеallow позволяет инференсу продолжиться; deny отклоняет его.
deny_reasonstring или null; не более 500 символов, более длинные значения усекаютсяПоказывается конечному пользователю, когда action равно deny; игнорируется при allow.
reference_idstring или null; не более 50 символов из [A-Za-z0-9._:/-]Ваш собственный идентификатор для этой оценки. Он записывается в активность соответствия inference_hooks_request_denied для отклонения и никогда не показывается конечному пользователю. Держите его непрозрачным: никакого содержимого запроса и никаких персональных данных.

Deny никогда не отбрасывается из-за проблемы форматирования: deny_reason чрезмерного размера усекается, некорректный reference_id молча отбрасывается, а action по-прежнему соблюдается.

Обратное неверно. Всё, кроме HTTP 200 с разбираемым вердиктом, является сбоем вебхука, и вместо вердикта применяется обработка сбоев вашей организации. В частности:

  • Не сигнализируйте deny статусом ошибки. Ответ, отличный от 200, — это сбой, а не deny.
  • Любое значение action, отличное от allow или deny, рассматривается как сбой вебхука.

Anthropic читает не более 64 КиБ тела ответа, и тело должно быть несжатым. Перенаправления не выполняются, а cookie игнорируются. Неизвестные поля в теле вердикта игнорируются, поэтому вы можете возвращать более богатый объект наряду с полями, задокументированными здесь.

Проверка подписи

Запросы подписываются согласно спецификации Standard Webhooks с использованием трёх заголовков. Anthropic отправляет имена заголовков в нижнем регистре, а прокси могут свободно менять их регистр, поэтому ищите их без учёта регистра.

ЗаголовокСодержимое
webhook-idУникальный идентификатор этой доставки. Равен request_id в теле. Используйте его как ключ идемпотентности и как первый компонент подписываемой полезной нагрузки.
webhook-timestampВремя Unix в секундах в виде десятичной строки, когда запрос был подписан. Отклоняйте метку времени, отличающуюся от часов вашего сервера более чем на пять минут в любую сторону.
webhook-signatureОдно или несколько разделённых пробелами значений v1,<base64>, каждое — HMAC-SHA256 от {webhook-id}.{webhook-timestamp}.{raw body bytes}. Принимайте запрос, если любое значение совпадает с вашим, используя сравнение за постоянное время.

Две детали вызывают большинство ошибок проверки:

  • Проверяйте сырые байты. Вычисляйте HMAC по телу точно в том виде, в каком оно получено, до любого разбора JSON или перекодирования.
  • Декодируйте секрет стандартным декодером base64. Секрет подписи — это значение после префикса whsec_, закодированное стандартным алфавитом base64 (+ и /), как и подпись в заголовке. URL-безопасный декодер выводит неверные байты ключа всякий раз, когда секрет содержит + или /, что происходит в большинстве случаев.

Как только у вашей организации появляется секрет подписи, каждый запрос, отправляемый Anthropic, подписывается, а для включения Inference hooks он обязателен, поэтому отклоняйте любой запрос, пришедший без подписи. Одно исключение: тест соединения, отправленный до первого сохранения настроек вашей организации, приходит без подписи, потому что секрет подписи ещё не существует. Принимайте неподписанные запросы, пока ваш администратор не подтвердит, что секрет существует, а затем отклоняйте их.

Ротация секрета — это немедленное переключение, но запросы, подписанные предыдущим секретом, всё ещё могут приходить примерно в течение минуты после этого, плюс всё, что уже находится в пути. Пусть ваш сервер безопасности ИИ принимает подписи от обоих секретов во время переключения, чтобы эти опоздавшие запросы не отклонялись.

Следующие примеры являются серверными реализациями, поэтому вкладки shell нет: сервер безопасности ИИ — это долго работающий HTTPS-сервис, а не одноразовый запрос. Каждый пример использует только стандартную библиотеку языка; проект Standard Webhooks также публикует библиотеки проверки для большинства языков.

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

    # Сравниваем байты: compare_digest для str выбрасывает исключение при не-ASCII вводе.
    return any(
        hmac.compare_digest(expected, candidate.encode())
        for candidate in signatures.split()
    )

Операционная семантика

Тайм-аут и повтор

Ваш администратор задаёт тайм-аут вердикта от 1 до 10 000 мс (по умолчанию 5000 мс). Бюджет покрывает весь обмен: подключение, TLS-рукопожатие, запрос и ответ.

Anthropic повторяет попытку ровно один раз, после задержки в 100 мс, и только когда попытка подключения завершается неудачей. Повтор использует тот же бюджет тайм-аута и несёт тот же webhook-id и ту же подпись. Как только ваш сервер безопасности ИИ ответил, обмен никогда не повторяется.

Сбои вебхука

Тайм-ауты, статусы, отличные от 200 (включая перенаправления), неразбираемые или чрезмерно большие тела ответов и недоступные конечные точки — всё это сбои вебхука. Сбой вебхука никогда не превращается в deny; вместо этого настройка обработки сбоев вашей организации решает, будет ли затронутый запрос заблокирован или продолжится без проверки.

Автоматический предохранитель

Устойчивые сбои вебхука, относимые к вашему серверу безопасности ИИ, приводят к срабатыванию «circuit breaker» (автоматического предохранителя), который останавливает принудительное применение: Anthropic прекращает обращаться к вашему серверу, и обработка сбоев применяется к каждому запросу.

Начиная с 10 минут после срабатывания Anthropic проверяет, восстановился ли ваш сервер: не чаще примерно одного раза в минуту один запрос, переносимый собственным трафиком вашей организации, доставляется на ваш сервер для проверки, подписанный и оформленный как любой другой. Отвечайте на него обычным образом. Корректный вердикт, allow или deny, сбрасывает предохранитель, и принудительное применение возобновляется. Сбой вебхука оставляет предохранитель сработавшим, и проверка продолжается. В любом случае сам тестовый запрос продолжается для своего пользователя: его вердикт не применяется принудительно, и неудачный тест не блокирует его, даже при Block the request. Администратор также может сбросить предохранитель в любое время, а изменения конфигурации администратором останавливают автоматическую проверку; см. Автоматический предохранитель.

Каждое срабатывание записывается как активность inference_hooks_circuit_breaker_tripped в Activity Feed (ленте активности), по одной активности на срабатывание. Пока предохранитель сработал, никакие активности Inference hooks по отдельным запросам не записываются, поэтому активность срабатывания — единственная запись в ленте о периоде срабатывания.

Задержка

Принудительное применение добавляет полный цикл обращения к вашему серверу безопасности ИИ к «latency» (задержке) каждого контролируемого запроса в вашей организации. Делайте вердикт быстрым и проведите нагрузочное тестирование вашего сервера перед развёртыванием в большой организации.

Исходные IP-адреса

Запросы к вашему серверу безопасности ИИ исходят из 160.79.106.0/24, части опубликованных Anthropic исходящих диапазонов IP. Добавьте в список разрешённых этот блок, а не входящие диапазоны на той же странице, которые его не покрывают. Список разрешённых сужает поверхность атаки вашего сервера, но не заменяет проверку подписи: этот блок несёт исходящий трафик Anthropic не только для Inference hooks.

Прямая совместимость

Протокол развивается, не ломая корректно написанные серверы. Ваш сервер должен игнорировать:

  • Неизвестные поля верхнего уровня в кадре подсказки.
  • Неизвестные ключи в metadata.
  • Новые значения source.application.
  • Новые значения actor.type. actor — это объединение, различаемое по type, и "user" — единственный вид, отправляемый сегодня; будущий вид гарантирует только наличие type.
  • Блоки содержимого с нераспознанным type.

Никогда не отклоняйте запрос из-за нераспознанного типа блока или поля; читайте поля, которые вы знаете, и пропускайте остальные.

В будущем будут введены другие типы событий хука. Новый тип события — это дополнение, которое ваш сервер не может обработать пропуском поля: запросу всё равно нужен вердикт. Когда type верхнего уровня имеет значение, которое вы не распознаёте, возвращайте вердикт allow, а не статус ошибки; ответ с ошибкой — это сбой вебхука, а устойчивые сбои приводят к срабатыванию автоматического предохранителя.

Проектирование вашей интеграции

Продакшн-сервер безопасности ИИ принимает несколько проектных решений помимо протокола передачи.

Дедуплицируйте по webhook-id. Заголовок webhook-id уникален для каждой доставки и равен request_id в теле, а повтор при сбое подключения использует его повторно, поэтому он работает как ключ идемпотентности. Если вы записываете вердикты, используйте его в качестве ключа записей.

Записывайте вердикты и сопоставляйте отклонения. Сохраняйте каждый возвращаемый вердикт вместе с его reference_id. Каждое отклонение записывается как активность соответствия inference_hooks_request_denied, содержащая reference_id, который вернул ваш сервер, поэтому вы можете сопоставлять отклонения в Activity Feed с соответствующими записями в вашей собственной системе.

Архивируйте с помощью всегда разрешающего сервера. Чтобы захватывать транскрипты в реальном времени, не контролируя их, безусловно возвращайте {"action": "allow"} и сохраняйте кадр после ответа. Это push-альтернатива опросу Compliance API, а ответ до сохранения убирает ваш полный цикл из критического пути пользователя.

Пишите deny_reason для конечного пользователя. Возвращаемый вами текст — это то, что видит пользователь, когда его запрос заблокирован, усечённый до 500 символов. Скажите ему, что изменить, например какой вид содержимого удалить, вместо того чтобы выдавать код сканера, который может интерпретировать только ваша команда.

Следующие шаги

Включите Inference hooks, подключите и протестируйте вашу конечную точку и управляйте принудительным применением, обработкой сбоев и развёртыванием.

Что такое Inference hooks, как работает полный цикл вердикта и когда их использовать.

Was this page helpful?