Вебхуки
Обновлено
Вебхук избавляет от опроса: как только генерация завершилась, мы сами шлём POST-запрос на ваш адрес с её итоговым состоянием. Опрос при этом остаётся рабочим способом — вебхук его дополняет, а не заменяет. Как генерация запускается и что возвращает опрос, описано в статье Запуск генерации.
Подписка
Подписки заводятся в настройках, раздел «Интеграции». У подписки два поля:
- Адрес — куда слать. Только публичный адрес; внутренние и приватные отвергаются при сохранении. Он же называет подписку в списке.
- События — какие исходы вам нужны.
При создании выдаётся секрет подписи. Он показывается один раз — как и ключ API, дальше он хранится только у вас.
Адрес проверяется в момент сохранения: если имя не разрешается в публичный адрес, подписка не создастся.
События
Событий три, по числу конечных состояний генерации:
generation.completed— работа закончена, результат готов;generation.failed— работа провалилась;generation.cancelled— генерация отменена.
Промежуточных переходов (постановка в очередь, начало работы) в рассылке нет намеренно: интегратору важен исход, а поток всех переходов пришлось бы фильтровать на своей стороне.
Тело запроса
Приходит JSON:
{
"id": "9f1c…",
"event": "generation.completed",
"createdAt": "2026-08-02T10:15:31.204Z",
"data": { "generation": { "generationId": "3f2a…", "state": "ready", "…": "…" } }
}
Генерация внутри data — в той же форме, что и ответ GET /api/v1/generations/{id}. Одна и та же работа не должна выглядеть по-разному в зависимости от того, пришла она сама или её спросили.
Поле id — идентификатор доставки. Он одинаков у всех попыток одного события: по нему приёмник глушит повторы.
createdAt — момент события, а не момент попытки. У повторной доставки он тот же, что у первой.
Проверка подписи
Каждый запрос несёт заголовок X-Strophe-Signature:
X-Strophe-Signature: t=1785974131,v1=6b1f…
t — момент подписи в секундах Unix, v1 — HMAC-SHA256 от строки t.тело с вашим секретом, в шестнадцатеричном виде. Проверка на стороне приёмника:
import hashlib, hmac, time
def valid(raw_body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
signed = f"{parts['t']}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
fresh = abs(time.time() - int(parts["t"])) < 300
return fresh and hmac.compare_digest(expected, parts["v1"])
Считать подпись нужно от сырого тела запроса, до разбора JSON: повторная сборка объекта в строку даёт другие пробелы и другой порядок ключей, а значит другую подпись.
Момент t стоит сравнивать с текущим временем и отвергать слишком старые запросы: без этого перехваченный когда-то запрос можно проиграть заново.
Запрос без действительной подписи обрабатывать нельзя. Адрес приёмника рано или поздно становится известен, и подпись — единственное, что отличает наше уведомление от чужого.
Требования к приёмнику
Успех — только код 2xx. Всё остальное считается неудачей и приводит к повтору. Редиректы не выполняются: приёмник обязан отвечать по тому адресу, на который подписался.
Ответить нужно за 10 секунд. Долгая работа по событию выносится в свою очередь: сначала подтвердите приём, потом обрабатывайте.
Повторы возможны. Сеть не гарантирует однократной доставки: приёмник должен переживать повтор того же id без последствий.
Порядок не гарантирован. Событие по одной генерации может обогнать событие по другой; опираться нужно на содержимое, а не на очерёдность прихода.
Повторные попытки
Неудачная доставка повторяется до шести раз с удваивающейся паузой, начиная с десяти секунд, — это около двадцати минут на приёмник, упавший под нагрузкой. Исчерпав попытки, доставка закрывается как неудачная.
Первые несколько байт ответа приёмника мы сохраняем: если он объяснил отказ текстом, этот текст будет виден в журнале.
Журнал доставок
У каждой подписки есть журнал: строка на событие с временем, номером события, числом попыток, кодом ответа и текстом отказа. Он открывается из меню подписки в настройках и отвечает на вопрос «почему мне не пришло» без обращения в поддержку.
Журнал хранится 30 дней — хвост чистится сам.
В строке подписки в списке видны день последней доставки и её исход: так поломка заметна раньше, чем о ней сообщит журнал.