Интеграция Inference hooks — это сервер безопасности ИИ: 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 отклоняются на этапе подключения), с сертификатом, который проходит проверку по публичному хранилищу доверенных CA, отвечающий без перенаправлений. Настроенный URL должен быть конечным адресом. В разделе Настройка Inference hooks описано, как ваш администратор задаёт и тестирует URL.
Каждый запрос содержит следующие фиксированные заголовки, а также любые пользовательские заголовки запроса, настроенные вашим администратором, и — как только у вашей организации появится секрет подписи — заголовки подписи webhook-*, описанные в разделе Проверка подписи:
| Заголовок | Значение |
|---|---|
Content-Type | application/json |
User-Agent | anthropic-dlp/1 |
Accept-Encoding | identity |
На сегодняшний день существует одно событие хука: «prompt frame» (кадр подсказки), отправляемый один раз на каждый контролируемый запрос инференса, до начала инференса. Anthropic удерживает запрос до тех пор, пока ваш сервер безопасности ИИ не ответит или не истечёт тайм-аут вердикта.
Тело запроса — это JSON-объект со следующими полями:
| Поле | Тип | Описание |
|---|---|---|
type | string | Событие хука. На сегодняшний день всегда "prompt"; в будущем будут введены другие типы событий, поэтому корректно обрабатывайте нераспознанное значение (см. Прямая совместимость). |
request_id | string | Непрозрачный идентификатор для каждого вызова инференса, используемый для корреляции. Равен заголовку webhook-id. |
tenant_id | string или null | Непрозрачный идентификатор организации, которой принадлежит запрос. |
actor | object | Субъект, которому приписывается запрос, различаемый по type ("user" — единственное значение, отправляемое на сегодняшний день): id (тегированный идентификатор, стабильный между запросами для одной и той же учётной записи) и email_address (если доступен). И id, и email_address могут быть null. |
source | object | Приложение-источник: application (см. Значения source). |
messages | array | Транскрипт разговора до момента инференса. См. Блоки контента. |
session_id | string или null | Непрозрачный идентификатор разговора, если он существует. Не парсите его. Для Claude Code это идентификатор сессии, заявленный клиентом по принципу best-effort. |
model | string или null | Публичный идентификатор модели для этого запроса, если доступен. |
metadata | object | Зарезервированная карта расширений со строковыми ключами и строковыми значениями, на сегодняшний день отправляется пустой. Не требуйте от неё ничего и допускайте её отсутствие, присутствие и любые появляющиеся ключи. |
Пример тела запроса:
{
"type": "prompt",
"request_id": "req_abc123",
"tenant_id": "11111111-1111-1111-1111-111111111111",
"actor": {
"type": "user",
"id": "user_01AbCdEfGhIjKlMnOpQrStUv",
"email_address": "[email protected]"
},
"source": {
"application": "claude-ai"
},
"session_id": "22222222-2222-2222-2222-222222222222",
"model": "claude-sonnet-4-5",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "Summarize the attached report."
},
{
"type": "attachment",
"file_name": "q2-report.pdf",
"media_type": "application/pdf",
"size_bytes": 48213,
"text": "Q2 revenue grew 14% quarter over quarter..."
}
]
}
],
"metadata": {}
}Каждый элемент в messages имеет role со значением user или assistant (результаты инструментов появляются под ролью user, что соответствует модели контента публичного Messages API) и массив content из блоков, различаемых по type:
type блока | Поля |
|---|---|
text | text: текстовое содержимое. |
tool_use | id: идентификатор, на который ссылается соответствующий результат инструмента. tool_name: имя инструмента. input: аргументы, которые модель передала инструменту. |
tool_result | content: вывод инструмента в виде текста, части которого соединены переводами строк; бинарные части, такие как изображения, заменяются маркерами-заполнителями, а необработанные байты никогда не отправляются. is_error: указывает, завершился ли вызов инструмента ошибкой. tool_name: имя инструмента, чтобы политика могла учитывать идентичность инструмента без перекрёстной ссылки на более ранний блок. tool_use_id: id соответствующего блока tool_use. |
attachment | file_name: исходное имя файла или путь. media_type: медиатип вложения. size_bytes: размер исходного файла. text: текстовое содержимое вложения, если доступно, например извлечённый текст документа, транскрипт аудио или метаданные ссылки. Необработанные байты вложения никогда не отправляются. |
Блок, type которого вы не распознаёте, является дополнением с прямой совместимостью. Единственное гарантированное поле — type; ваша политика может проверять любые другие присутствующие поля, но не должна отклонять запрос из-за нераспознанного типа.
Транскрипт — это разговор в том виде, в каком его видит конечный пользователь, до момента инференса: текст транскрипта, вызовы инструментов и их результаты, извлечённый текст вложений и предыдущие реплики. Он никогда не включает системные подсказки, определения инструментов, внутренний контекст Anthropic, скрытые рассуждения Claude или необработанные байты файлов.
Реплика, все блоки которой исключены, опускается полностью, поэтому не предполагайте строгого чередования user и assistant.
Транскрипты отправляются без усечения, поэтому длинный разговор с большими вложениями порождает большое тело запроса, вплоть до верхней границы в 10 МБ. Увеличьте лимит тела запроса на вашем сервере, чтобы принимать этот потолок. Несколько распространённых значений по умолчанию значительно меньше, включая client_max_body_size в nginx (1 МБ) и express.json() в Express (100 КБ), а отклонённое тело считается сбоем вебхука, поэтому при обработке сбоев Allow the request слишком большая подсказка достигнет модели без проверки.
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_reason | string или null; не более 500 символов, более длинные значения усекаются | Показывается конечному пользователю, когда action равно deny; игнорируется при allow. |
reference_id | string или null; не более 50 символов из набора [A-Za-z0-9._:/-] | Ваш собственный идентификатор для этой оценки. Он записывается в активность соответствия inference_hooks_request_denied для данного отклонения и никогда не показывается конечному пользователю. Держите его непрозрачным: без содержимого запроса и без персональных данных. |
Вердикт deny никогда не отбрасывается из-за проблемы форматирования: слишком длинный deny_reason усекается, некорректный reference_id молча отбрасывается, а action всё равно учитывается.
Обратное неверно. Всё, кроме HTTP 200 с разбираемым вердиктом, является сбоем вебхука, и вместо вердикта применяется обработка сбоев вашей организации. В частности:
action, кроме allow или deny, рассматривается как сбой вебхука.Anthropic читает не более 64 КиБ тела ответа, и тело должно быть несжатым. Перенаправления не выполняются, а cookies игнорируются. Неизвестные поля в теле вердикта игнорируются, поэтому вы можете возвращать более богатый объект наряду с полями, задокументированными здесь.
Запросы подписываются согласно спецификации Standard Webhooks с использованием трёх заголовков. Anthropic отправляет имена заголовков в нижнем регистре, а прокси могут изменять их регистр, поэтому ищите их без учёта регистра.
| Заголовок | Содержимое |
|---|---|
webhook-id | Уникальный идентификатор этой доставки. Равен request_id в теле. Используйте его как ключ идемпотентности и как первый компонент подписываемой полезной нагрузки. |
webhook-timestamp | Unix-время в секундах в виде десятичной строки, когда запрос был подписан. Отклоняйте временную метку, отличающуюся от часов вашего сервера более чем на пять минут в любую сторону. |
webhook-signature | Одно или несколько разделённых пробелами значений v1,<base64>, каждое из которых — HMAC-SHA256 от {webhook-id}.{webhook-timestamp}.{raw body bytes}. Принимайте запрос, если любое значение совпадает с вашим, используя сравнение за постоянное время. |
Две детали вызывают большинство ошибок проверки:
whsec_, закодированное стандартным алфавитом base64 (+ и /), как и подпись в заголовке. URL-safe декодер получает неверные байты ключа всякий раз, когда секрет содержит + или /, а это происходит в большинстве случаев.Как только у вашей организации появляется секрет подписи, каждый запрос, отправляемый 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 мс (по умолчанию 5 000 мс). Бюджет покрывает весь обмен: соединение, TLS-рукопожатие, запрос и ответ.
Anthropic повторяет попытку ровно один раз, после задержки в 100 мс, и только если попытка соединения не удалась. Повторная попытка использует тот же бюджет тайм-аута и несёт тот же webhook-id и ту же подпись. После того как ваш сервер безопасности ИИ ответил, обмен никогда не повторяется.
Тайм-ауты, статусы, отличные от 200 (включая перенаправления), неразбираемые или слишком большие тела ответа и недоступные эндпоинты — всё это сбои вебхука. Сбой вебхука никогда не становится вердиктом deny; вместо этого настройка обработки сбоев вашей организации определяет, будет ли затронутый запрос заблокирован или продолжится без проверки.
Устойчивые сбои вебхука, относимые к вашему серверу безопасности ИИ, срабатывают как «circuit breaker» (автоматический выключатель), который останавливает применение политики: Anthropic прекращает обращаться к вашему серверу, и обработка сбоев применяется к каждому запросу. Восстановление происходит на стороне администратора: исправьте сервер, затем попросите вашего администратора снова включить Enforce verdicts. См. Автоматический выключатель.
Применение политики добавляет время полного цикла вашего сервера безопасности ИИ к задержке каждого контролируемого запроса в вашей организации. Делайте вердикт быстрым и проведите нагрузочное тестирование вашего сервера перед развёртыванием в крупной организации.
Запросы к вашему серверу безопасности ИИ исходят из 160.79.106.0/24, части опубликованных исходящих диапазонов IP Anthropic. Добавьте этот блок в список разрешённых, а не входящие диапазоны с той же страницы, которые его не покрывают. Добавление в список разрешённых сужает поверхность атаки вашего сервера, но не заменяет проверку подписи: блок несёт исходящий трафик 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 с соответствующими записями в вашей собственной системе.
Архивируйте с помощью сервера, всегда возвращающего allow. Чтобы захватывать транскрипты в реальном времени, не контролируя их, возвращайте {"action": "allow"} безусловно и сохраняйте кадр после ответа. Это push-альтернатива опросу Compliance API, а ответ до сохранения убирает ваш полный цикл из критического пути пользователя.
Пишите deny_reason для конечного пользователя. Текст, который вы возвращаете, — это то, что видит пользователь, когда его запрос заблокирован, усечённый до 500 символов. Скажите ему, что изменить, например какой вид контента удалить, вместо того чтобы выдавать код сканера, который может интерпретировать только ваша команда.
Включите Inference hooks, подключите и протестируйте ваш эндпоинт, управляйте применением политики, обработкой сбоев и развёртыванием.
Что такое Inference hooks, как работает полный цикл вердикта и когда их использовать.
Was this page helpful?