Приём вебхуков
Вебхук - это наш POST-запрос на ваш адрес в момент, когда что-то произошло: заказ создан, оплачен, собран, отменён. Опрашивать /v1/orders по таймеру при этом не нужно.
Как включить
- В кабинете укажите адрес приёмника. Только
https, только порт 443, самоподписанный сертификат мы не примем. - Отметьте события, которые вам нужны. Подписка на всё подряд превращает журнал в шум.
- Скопируйте секрет подписи. Он показывается один раз и меняется отдельной кнопкой.
- Отправьте тестовое событие из кабинета и убедитесь, что приёмник ответил 2xx.
Что приходит
POST /hooks/cedar HTTP/1.1
Content-Type: application/json
X-Event-Id: 0f1c8ad4-5b21-4f77-9a30-2c6e1b884d10
X-Event-Type: order.paid
X-Event-Time: 2026-08-16T11:20:41Z
X-Signature: sha256=9c1f0b...
{
"id": "0f1c8ad4-5b21-4f77-9a30-2c6e1b884d10",
"type": "order.paid",
"order": {
"id": "ORD-20481",
"total": 387000,
"items": [ { "sku": "TRM-1180", "qty": 3 } ]
}
}Тело всегда JSON в UTF-8, время всегда UTC. Полей в объекте order со временем становится больше, поэтому разбирайте его по именам и не падайте на незнакомом ключе.
Проверка подписи
Подпись считается как HMAC SHA-256 от сырого тела запроса. Считать её по разобранному и заново собранному JSON нельзя: порядок ключей изменится, и подпись не сойдётся.
import crypto from 'node:crypto';
function valid(rawBody, header, secret) {
const mine = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const sent = String(header || '').replace('sha256=', '');
const a = Buffer.from(mine, 'utf8');
const b = Buffer.from(sent, 'utf8');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}timingSafeEqual и его аналоги существуют ровно для этого.Повторы и идемпотентность
Мы ждём ответ 5 секунд. Любой ответ 2xx считаем принятым, всё остальное - неудачей и повторяем доставку шесть раз: через минуту, 5 минут, 15 минут, час, 6 часов и сутки. После последней попытки подписка ставится на паузу, а в кабинет приходит уведомление.
X-Event-Id тот же самый. Храните принятые идентификаторы 72 часа и отвечайте на повтор 2xx, ничего не делая: сеть рвётся чаще, чем кажется, и заказ, собранный дважды, стоит дороже лишней таблицы.События
| Событие | Когда приходит |
|---|---|
order.created | Заказ создан на витрине, оплата ещё не подтверждена |
order.paid | Оплата подтверждена, заказ можно собирать |
order.assembled | Сборка завершена, ждём передачу в доставку |
order.shipped | Заказ передан в доставку, есть трек-номер |
order.canceled | Отмена целиком или последней позиции заказа |
stock.low | Остаток позиции опустился ниже порога, заданного в кабинете |
price.rejected | Цена не принята витриной: чаще всего ниже минимальной |
Отладка
Журнал доставок в кабинете хранит 30 дней: по каждому событию видно тело, заголовки, код ответа и время. Повторить доставку можно кнопкой - это нормальный способ починить приёмник после релиза, не дожидаясь следующего заказа.
Если приёмник отвечает 2xx, но событие потерялось у вас, смотрите X-Event-Id в своих журналах: он же лежит в журнале доставок и в ответе метода /v1/hooks/deliveries.