Вебхуки

Обновлено

Скачать Markdown

Вебхук избавляет от опроса: как только генерация завершилась, мы сами шлём 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 дней — хвост чистится сам.

В строке подписки в списке видны день последней доставки и её исход: так поломка заметна раньше, чем о ней сообщит журнал.