← Все инструкции

Уведомления: Callback и JSON-RPC события

Как получать уведомления о ходе и результате генерации — простой webhook или JSON-RPC-события, без ответа с вашей стороны.

Что это

Два независимых и более простых канала, чем полная доставка статей (см. «Приём статей на своём сайте»): Octopus сам стучится к вам, когда что-то происходит, но не ждёт от вас осмысленного ответа — только код 2xx как подтверждение получения.

  • Callback URL — один простой POST по завершении всей задачи. Без конверта JSON-RPC; подписывается тем же JSON-RPC secret, если он задан.
  • JSON-RPC события — task.started / article.completed / task.finished, каждое можно включить или выключить отдельно. Опционально подписаны HMAC.

Нужна не просто галочка «получено», а решение принять/отклонить конкретную статью — это уже другой механизм, article.deliver из гайда выше.

Шаг 1. Настройте нужный канал (или оба) в проекте

Всё — на странице проекта → блок «Интеграция».

  1. Callback URL — публичный HTTPS-адрес, куда прилетит один POST по завершении задачи. Пусто — колбэк не планируется вовсе.
  2. JSON-RPC endpoint — публичный HTTPS-адрес для событий (тот же, что использует article.deliver, если он тоже включён). JSON-RPC secret — опционален, но если задан, каждое событие подписывается (см. Шаг 3).
  3. Отметьте чекбоксы «JSON-RPC события»: какие из трёх присылать. Ничего не отмечено при том, что вы вообще не трогали этот блок — по умолчанию присылается только task.finished. Если явно снять все галочки и сохранить форму — не придёт вообще ничего, это тоже валидный осознанный выбор.

Оба адреса в приватных сетях (localhost, 127.0.0.1, серые IP) отбрасываются защитой от SSRF — endpoint должен быть доступен из интернета.

Шаг 2. Примите Callback URL (если настроили)

По завершении задачи (успех, провал или частичный провал) на ваш URL придёт:

{
  "event": "task.completed",
  "task_id": 45,
  "status": "partially_failed",
  "total_articles": 10,
  "completed_articles": 8,
  "failed_articles": 2,
  "total_tokens": 152340,
  "total_cost": "3.841200",
  "timestamp": "2026-08-28T09:12:00+00:00"
}
Поле Значение
statuscompleted / partially_failed / failed.
total_costСтрока (не число) — денежная точность, не подставляйте напрямую во float-арифметику.

Ни текста статей, ни article_id здесь нет — это сводка по всей задаче целиком. Подписи нет: любой, кто узнает URL, может дёрнуть его с произвольным телом — не доверяйте payload'у без дополнительной проверки, если это важно для вашей логики.

Шаг 3. Примите JSON-RPC события (если настроили)

Каждое событие — отдельный POST на JSON-RPC endpoint с телом вида:

{
  "jsonrpc": "2.0",
  "method": "<событие>",
  "params": { ... }
}

Обратите внимание — здесь нет id. Это осознанно: по JSON-RPC 2.0 отсутствие id и означает «notification», а не «запрос». Octopus не разбирает тело вашего ответа — только код 2xx как подтверждение.

task.started — задачу взял воркер

{"task_id": 45, "type": "generate", "total_articles": 10, "mode": "pipeline"}

type — generate или process. mode всегда "pipeline" — оставлено в контракте для старых интеграций.

article.completed — одна статья готова

{
  "task_id": 45, "article_id": 123, "title": "Заголовок статьи",
  "rubric": "Технологии", "model": "polza/gpt-4o-mini", "prompt_tokens": 1200,
  "completion_tokens": 2400, "total_tokens": 3600, "cost": "0.048600"
}

Только метаданные — текста статьи здесь нет. rubric определяется AI на стадии Reviewer и может быть null у пайплайнов без этой стадии. Нужен текст сразу с решением принять/отклонить — см. article.deliver.

task.finished — все статьи задачи завершены

{
  "task_id": 45, "status": "partially_failed", "total_articles": 10,
  "completed_articles": 8, "failed_articles": 2,
  "total_tokens": 152340, "total_cost": "3.841200",
  "finished_at": "2026-08-28T09:12:00+00:00"
}

По сути то же самое, что Callback URL из Шага 2 (событие task.completed там), но в конверте JSON-RPC.

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

Если задан JSON-RPC secret, событие приходит подписанным — тот же принцип, что и у article.deliver: X-Timestamp плюс X-Octopus-Signature-V2 (HMAC от «<X-Timestamp>.<тело>»). Старый X-Octopus-Signature (HMAC только от тела) продолжает приходить для обратной совместимости, но от повторной отправки перехваченного запроса не защищает.

$rawBody = file_get_contents('php://input');
$ts      = $_SERVER['HTTP_X_TIMESTAMP'] ?? '';

// Окно ±5 минут: без него подпись вечная и запрос можно проиграть заново.
if ($ts === '' || abs(time() - (int) $ts) > 300) {
    http_response_code(400);
    exit;
}

$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $rawBody, $secret);

if (!hash_equals($expected, $_SERVER['HTTP_X_OCTOPUS_SIGNATURE_V2'] ?? '')) {
    http_response_code(401);
    exit;
}

Без secret заголовка не будет вовсе — тогда endpoint принимает события от кого угодно, кто узнает его адрес.

Повторные попытки и отладка

  • Оба канала — best-effort и вне транзакции генерации: недоступный endpoint не ломает задачу.
  • Callback URL: до 7 попыток, интервалы 10 мин → 30 мин → 2 ч → далее раз в 2 ч. Очередь и история — страница /callbacks.
  • JSON-RPC события: до 5 попыток, тот же ритм (10 мин → 30 мин → 2 ч → 2 ч). Своей UI-страницы с историей у них нет — смотрите логи задачи (запись jsonrpc.scheduled).
  • В обоих случаях любой код вне диапазона 2xx, сетевая ошибка или таймаут (30 сек) — повод для повтора; тело вашего ответа не разбирается ни там, ни там.