# Webhook для AI API: подпись, повторы и безопасность

Практическая схема безопасного webhook для AI API: raw body, HMAC SHA-256, timestamp, защита от повторов, очередь и ротация секрета.

Канонический URL: https://llmtokenapi.ru/blog/webhook-dlya-ai-api-bezopasnost
Опубликовано: 2026-08-31T15:17:47.166Z
Автор: Команда LLMTOKENAPI

Webhook для AI API нужно принимать как недоверенный внешний запрос: проверить HMAC-подпись по исходному телу, ограничить допустимый возраст события и обработать уникальный event ID только один раз. Такая схема защищает асинхронные map, crawl и исследовательские задания от подмены, повторной доставки и двойного запуска бизнес-логики.

## Зачем webhook, если статус можно опрашивать

Долгая операция не должна удерживать пользовательское HTTP-соединение. Клиент создаёт задание, получает идентификатор и продолжает работу. Когда результат готов, сервис отправляет событие на заранее заданный URL. Это уменьшает число запросов статуса и позволяет сразу продолжить внутренний процесс: сохранить результат, обновить карточку или поставить следующий шаг в очередь.

Polling всё равно полезен как резервный путь. Webhook сообщает о событии, но не обязан быть единственным источником истины. Если доставка задержалась или ваш endpoint был недоступен, приложение может получить состояние через `GET /v1/jobs/{id}` либо прочитать журнал доставок зарегистрированного endpoint.

В LLMTOKENAPI для простого `map` или `crawl` можно передать `webhookUrl`. Для нескольких типов задач удобнее создать постоянный webhook endpoint и подписать его только на нужные события, например `job.completed` и `job.failed`. Актуальные маршруты и scopes перечислены в [документации API](/docs).

## Сначала сохраните секрет и сырой body

Секрет webhook возвращается при создании endpoint и должен храниться так же внимательно, как API-ключ. Не записывайте его в репозиторий, браузерный код или обычные логи. При компрометации перевыпустите секрет и обновите принимающую сторону.

Для проверки подписи нужен именно исходный набор байтов HTTP-тела. Если сначала разобрать JSON, а потом сериализовать объект заново, изменятся пробелы или порядок полей, и корректная подпись перестанет совпадать. Поэтому обработчик должен сначала прочитать raw body, проверить запрос и только затем выполнять `JSON.parse`.

[Руководство GitHub по проверке webhook](https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries) использует тот же базовый принцип: вычислить HMAC от полученного тела с секретом и сравнить результат безопасным способом. Для LLMTOKENAPI подписываемая строка имеет форму `timestamp.eventId.body`, а служебные значения приходят в заголовках:

- `x-llmtokenapi-event-id` — стабильный идентификатор события;
- `x-llmtokenapi-timestamp` — время создания подписи;
- `x-llmtokenapi-signature` — подпись HMAC SHA-256 с версией `v1`.

## Проверяйте подпись до разбора события

Надёжный обработчик выполняет проверки в фиксированном порядке:

1. Убеждается, что все три заголовка присутствуют и имеют ожидаемый формат.
2. Проверяет, что timestamp попадает в небольшое допустимое окно относительно серверного времени.
3. Собирает строку `timestamp.eventId.rawBody` без преобразования тела.
4. Вычисляет HMAC SHA-256 с сохранённым секретом.
5. Сравнивает подписи функцией постоянного времени.
6. Проверяет event ID в хранилище обработанных событий.
7. Только после этого разбирает JSON и запускает бизнес-логику.

Обычное сравнение строк через `===` не подходит для криптографической проверки. Используйте `timingSafeEqual` в Node.js или эквивалент вашей платформы. Перед сравнением убедитесь, что буферы одинаковой длины, иначе многие реализации выбросят исключение.

## Защититесь от повторной доставки

Даже правильно подписанное событие может прийти повторно. Причина обычно безобидна: принимающий сервер успел обработать запрос, но ответ потерялся, поэтому отправитель повторил доставку. Если обработчик второй раз создаст заказ, письмо или платную задачу, подпись не спасёт от дубля.

Храните `eventId` в таблице с уникальным индексом. В одной транзакции попробуйте зафиксировать ID и выполнить изменение состояния. Если запись уже существует, верните успешный ответ без повторной работы. Такой подход превращает повторную доставку в нормальный сценарий, а не в инцидент.

Проверка возраста подписи решает другую задачу: не позволяет бесконечно воспроизводить старый корректный запрос. Практическое окно зависит от вашей очереди и сетевой задержки; выбирайте его явно и следите за синхронизацией времени на сервере. Сочетание timestamp и уникального event ID защищает лучше, чем любой из этих механизмов по отдельности.

## Отвечайте быстро, тяжёлую работу выполняйте в очереди

Webhook endpoint должен проверить запрос, надёжно сохранить событие и быстро вернуть код `2xx`. Извлечение большого результата, отправку писем или обновление поискового индекса лучше передать внутреннему worker. Иначе принимающая сторона может превысить таймаут, а отправитель начнёт повторять уже выполняющуюся работу.

Разделите статусы «событие принято» и «бизнес-операция завершена». Первый означает, что payload сохранён и больше не потеряется. Второй обновляется worker после фактической обработки. Для диагностики сохраняйте event ID, тип события, время получения, результат проверки и внутренний request ID, но не секрет и не чувствительное содержимое пользовательских данных.

## Подготовьте endpoint к ошибкам и ротации

Перед production проверьте положительный и отрицательный сценарии. Корректная подпись должна приниматься; изменённое тело, старый timestamp и неверная подпись — отклоняться. Два запроса с одним event ID должны менять состояние только один раз. Временно недоступная база должна приводить к повторяемой ошибке, а не к потере события.

Постоянный webhook endpoint в LLMTOKENAPI можно протестировать, перевыпустить его секрет, посмотреть доставки и повторить конкретную неудачную доставку. Эти операции требуют отдельных scopes `webhooks:read` и `webhooks:write`, поэтому ключ для управления webhook не нужно использовать в обычном пользовательском трафике.

Итоговый webhook для AI API состоит не из одного публичного URL. Это проверка raw body, HMAC SHA-256, короткое окно времени, уникальность event ID, быстрая фиксация события и фоновая обработка. С такой схемой повторная доставка становится безопасной, а асинхронные задания — наблюдаемыми и предсказуемыми.
