Использование

Эта страница описывает, что приходит на ваш URL и как на это отвечать. Полные схемы полей по каждому событию — в OpenAPI-спецификации.

Конверт запроса

Все события приходят POST-запросом с телом в формате JSON и единым конвертом:

JSON
{ "id": "0193…uuid", "event": "order.created", "occurred_at": "2026-05-11T10:00:00Z", "api_version": "1", "data": { } }
  • id — идентификатор доставки, не меняется между повторами одного события.
  • event — имя события (см. список).
  • occurred_at — момент события (ISO-8601, UTC).
  • api_version — версия контракта payload.
  • data — содержимое, специфичное для события. Схема — в OpenAPI документации.

Заголовки

Заголовок Значение
Content-Type application/json
User-Agent Mobius-Webhooks/1.0
X-Mobius-Event имя события
X-Mobius-Delivery идентификатор доставки (одинаков для всех попыток одного события)
X-Mobius-Timestamp unix-секунды момента генерации запроса
X-Mobius-Signature t=<timestamp>,v1=<hex hmac_sha256(timestamp + "." + тело, секрет)>

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

Подписчик обязан проверять подпись и таймстемп. Это защищает от подделки запросов и от повторной отправки старого запроса.

Алгоритм:

  1. Возьмите сырое тело запроса (до парсинга JSON).
  2. Из заголовка X-Mobius-Signature разберите t (таймстемп) и v1 (подпись).
  3. Вычислите hmac_sha256(t + "." + raw_body, секрет) своим секретом подписки.
  4. Сравните с v1 в постоянном времени.
  5. Отклоните запрос, если подпись не совпала или t старше 5 минут.

Пример на PHP:

PHP
function verifyMobiusSignature(string $rawBody, string $header, string $secret): bool { // header: "t=1715420400,v1=abc123…" parse_str(str_replace(',', '&', $header), $parts); $t = $parts['t'] ?? ''; $v1 = $parts['v1'] ?? ''; if ($t === '' || $v1 === '') { return false; } // Защита от повторной отправки: не старше 5 минут. if (abs(time() - (int) $t) > 300) { return false; } $expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret); return hash_equals($expected, $v1); }

Пример на Node.js:

JS
const crypto = require('crypto'); function verifyMobiusSignature(rawBody, header, secret) { const parts = Object.fromEntries(header.split(',').map(p => p.split('='))); const t = parts.t, v1 = parts.v1; if (!t || !v1) return false; if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; const expected = crypto .createHmac('sha256', secret) .update(`${t}.${rawBody}`) .digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1)); }

Секрет берётся в админ-панели — действие «Секрет» в строке события (см. Доступ и настройка).

Идемпотентность

Async-события могут прийти повторно (например, ваш сервер ответил, но ответ не дошёл). Используйте X-Mobius-Delivery (он же id в конверте) как ключ идемпотентности: если доставку с таким идентификатором уже обработали — верните 2xx, но не выполняйте действие второй раз.

Контракт ответа

Async-события

order.created, order.paid, order.cancelled, bonus.*. Достаточно вернуть любой 2xx. Тело ответа не используется.

Если вернуть не-2xx или не ответить вовремя — доставка повторяется с возрастающей паузой (60 / 300 / 900 / 1800 секунд) до заданного в подписке числа повторов.

Sync-события

order.calculate, order.bonus.calculate, catalog.bonus.calculate. Ответ влияет на расчёт, поэтому вернуть нужно 2xx со строго одним из двух тел:

JSON
{ "ok": true, "data": { } }
JSON
{ "ok": false, "error": { "code": "string", "message": "string" } }
  • При ok: true поле data содержит переопределённые значения, специфичные для события (состав корзины, суммы, бонусы — см. OpenAPI документацию).
  • При ok: false, а также при любом отклонении (не-2xx, невалидный JSON, таймаут) — расчёт продолжается по умолчанию, без применения вашего ответа. Факт ошибки фиксируется в журнале.

Это «мягкий» контракт: ошибка подписчика никогда не ломает оформление заказа или выдачу каталога — она лишь означает «считай по умолчанию».

Журнал доставок

Вкладка «Журнал» в разделе вебхуков показывает историю доставок: событие, режим, статус, HTTP-код ответа, время ответа, номер попытки и время. Действие «Подробнее» раскрывает тело запроса и ответа.

В журнал попадают все попытки доставки async-событий и все ошибки sync-событий. Успешные sync-вызовы (включая частый catalog.bonus.calculate) не логируются — иначе журнал быстро переполнился бы.

Связанные разделы

Обновлено 10.07.2026 09:36
Помогла ли статья?