Webhooks: events delivered to your endpoint, signed, at least once
/docs/webhookscurrentМашинный перевод с английского· проверено 2026-09-24
Статус: current · проверенный: 2026-09-24, против сервера code и а подписанный delivery Реальный получатель (§ 8)) · владелец: гладиатр API документация · Служить: GET /docs/webhooks · Машинный справочник: GET /docs/events.json Соседи: `orders.md` (Что за order snapshot содержит), `promotions.md` (why promotion.changed Не имеет цены), `catalogue.md` (перечитывая а product after product.updated).
1.Что вы можете сделать здесь
Get Вместо опроса, когда что-то происходит в shop: ан order это created, paid, refunded или перемещается между статусами, а product изменения, обзор является утвержденный, а promotion или ценообразование rule изменения, а form Приходит лид, купон выданный покупателю, site chrome Каждый подписчик называет свой конечная точка URL, а set из events и а secret; Платформа POSTs подписана JSON конверт и повторы на неудачу.
Webhook является Оптимизация, а не source Истина. Каждый event именовать объект id. Четыре order.* events Также нести full order snapshot (§6.1 - достаточно для создания или обновления order в твоем CRM Без обратного чтения; другой events нести ссылку, и объект читается через нормальный Если пропустить одно уведомление, это навредит вам., keep периодический примирение посредством updated_from (§4.3).
2. Решать first (вилки)
| Если ты хочешь… | Возьми это path | Стоимость |
|---|---|---|
| В течение нескольких секунд реагировать на orders, leads, платежи | Подписывайтесь (этот раздел) и также периодически примиряться | Конечная точка, которая отвечает 2xx в менее чем 10 s и раздувать event_id |
| Один-off import или отчет | Оценить конечные точки (/docs/catalogue, /docs/orders); Нет подписки | Нечего принимать |
Чтобы знать price Покупатель видит after promotion.changed | Перечитывайте product/cart — тот event перевозить Нет цены | Один extra GET; смотреть /docs/promotions для why Цены вычисляются, а не хранятся |
Кто может подписаться. Подписка управляется подписанное лично в кабинет (API доступ → Webhooks) или через ApiAccess Сервис с сеансом. Личный токен cannot Создавать, редактировать или удалять их (PAT_METHOD_NOT_ALLOWED). События, которые несут в себе личные или commercial data — Все кроме разве что кроме product.updated, review.created, design.changed и task.* — может быть подписан на только в том, shop owner или администратор; ан editor's Попытка была отклонена. Тот же самый шлюз применяется к редактированию, удалению и test/ping Существующая конфиденциальная подписка. Обычный members Не вижу таких подписок в GET /webhooks: ни того, ни другого приемник URL (которые могут содержать данные или их HMAC secret раскрытый. Owner/admin сеансы сохраняют полную конфигурацию. empty Редактировать и Проверка удаления обычного члена event Атомная чувствительность к current row, Так что одновременный owner изменение cannot Разоблачить или удалить защищенную подписку.
3. Объекты и где они находятся live
Подписка (ApiAccess, только сеанс):
| Звони. | Что он делает |
|---|---|
GET /webhooks | список видимых подписок компании, новейшие first; чувствительные подписки owner/admin только |
POST /webhooks {url, events[], secret?} | создавать; url должен быть public http(s) — обратный ход, RFC1918, link-Отказ в адресах местных и метаданных (SSRF); 1–32 events из белого списка, дубликаты рухнули; secret empty → (32-х х) и Возвращаясь к разрешенной сессии (Также присутствует в списке подписок), own secret 16-64 чел. |
PUT /webhooks/{id} | частичный монтаж: url, events (не-empty = заменить), secret, is_active |
DELETE /webhooks/{id} | удалять |
POST /webhooks/{id}/test | синхронный пинг webhook.test с data.webhook_id; приемник's HTTP status и error text Вернитесь в ответ и храните в last_status/last_error |
Каждая подписка несет last_status (0 = never доставляемый или сетевой error), last_error, last_delivered_at — Кабинет показывает их.
конверт — одинаково для каждого event; тот body это JSON, Content-Type: application/json:
{
"event": "order.paid",
"event_id": "6c0b3c8a-2d3e-4f0a-9c1e-1f2a3b4c5d6e",
"created_at": "2026-09-10T12:34:56Z",
"data": {
"order_id": 1024, "order_no": "2609-17", "status": 1, "status_code": "new",
"total": 45892, "currency": "RUB", "payment_status": "paid",
"contact": { "name": "…", "phone": "…", "email": "…" },
"items": [ { "product_id": 5, "name": "…", "quantity": 1, "unit_price": 45892, "line_total": 45892, "key": "", "data": null } ],
"…": "the rest of the order snapshot, §6.1",
"payment_id": 77, "amount": 4589200, "provider": "robokassa", "kassa_id": 3, "payment_contour": "live"
}
}event_idОдин на одного event, Подписка, которую получает каждый Название: Deduplicate on It.created_atЭто момент, когда event случилось, UTC RFC 3339 — order По этому, не по прибытии.dataэто event-Специальный (§6).order.*Это full order snapshot (§6.1; для всех остальных event Это а ссылка: ids Плюс несколько fields Нужно было решить, читать ли объект..
Запросить заголовки:
| Заголовок | ценность |
|---|---|
X-Vizen-Event | event name, то же самое, что event в body |
X-Vizen-Event-Id | то же самое, что event_id |
X-Vizen-Delivery-Id | id из этого delivery row (event × подписка; отсутствует webhook.test |
X-Vizen-Attempt | попытка number, 1-я база; отсутствует на webhook.test |
X-Vizen-Timestamp | Когда эта попытка была предпринята, RFC 3339 UTC |
X-Vizen-Signature | sha256=<hex> — См. §4 |
User-Agent | Vizen-Webhooks/1.0 |
4. Рецепты
4.0 Формы, викторины и CRM совет
The UI может быть платформой form, его всплывающее окно или ваш own стильный form/quiz. Они submit на одной и той же платформе form schema и свинцовое потребление; lead.created Это исходное уведомление, а не конечная точка отправки браузера. примеры: /docs/forms, Особенно его test режимы.
Разоблачить все chain Когда вы передаете интеграцию: form id → sandbox_id → колонка впуска → свинец → подписка на webhook. GET /sandboxes список CRM Советы; создание веб-хука не создает доску. Отдельная доска это created и, в частности, выделены и выделены form. CRM sandbox, dev draft и HTML каркас sandbox Три разных вещи; а CRM Совет не является изолированным test окружающей среды. Его мутации live Операции, недоступные для dev ПАТ.
Есть два разных теста. Аутентификация owner JWT dev-preview представления есть test leads С подавленными поставками. /forms/{id}/test-lead умышленно посылать test уведомления, и /webhooks/{id}/test Вызовите своего получателя. А. dev PAT прикрепленный к public POST /form-leads Он был понижен до анонимного и не select dev mode. Не используйте этот запрос как delivery-free test. ПАТ cannot Настройка подписки на webhook; используйте owner's сессия (§ 2)).
4.1 Проверить подпись (обязательно))
X-Vizen-Signature это sha256= + хулиган(HMAC-SHA256(secret, сырой запрос body)). Подпись находится над exact bytes Получить — не перепрофилировать JSON before Проверка. Сравнение в постоянном времени.
import hmac, hashlib
def verify(secret: str, raw_body: bytes, header: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header or "")mac := hmac.New(sha256.New, []byte(secret))
mac.Write(rawBody)
want := "sha256=" + hex.EncodeToString(mac.Sum(nil))
ok := hmac.Equal([]byte(want), []byte(r.Header.Get("X-Vizen-Signature")))Проверить: отправить POST /webhooks/{id}/test от кабинета; получатель должен Ответ 2xx и ответ status должно быть 200-299. secret появляться как неисправность проверки на вашем side, не как error с платформы.
4.2 Получить
- Читать сырой body, проверить §4.1, отклонить 401 иным образом.
- Смотри.
event_idВ магазине, если вы видите, ответьте 200 Не делай ничего (The Do Nothing) Платформа перезагружается до тех пор, пока не получит 2xx, поэтому дубликат является нормальным.). - упорствовать
{event_id, event, created_at, data}и ответить 2xxx немедленно. Делайте реальную работу асинхронно: delivery Тайм-аут - 10 s включая связь и TLS; медленный обработчик зарабатывает повторный, а не more время. - Для
order.*тот body это тот order: number, stage, contact, address, items, методы, суммы и payment ii) государство (§6.1).order_id;GET /orders/{id}Он нужен только для того, что event Не перевозит — чат покупателя-продавца и order Журнал для других. events, когда работа объект нуждается в product, вести),GETс помощью id — Не реконструировать его изdata.
проверять: after настоящий event, GET /webhooks шоу last_status 2xx и свежий last_delivered_at.
4.3 Согласие — сохранение CRM в синхронизированном с orders
Рецепт The shop owner sets для одного external CRM:
- Подписаться (кабинет, owner или администратора) всем четырем order events:
order.created,order.status_changed,order.paid,order.refunded. Каждый body Это full snapshot (§6.1 - вставленоorder_id, keep тот новейшийupdated_at(events не заказывается, §5). - Опрос раз в час с персональным токеном (
orders:read, prod contour) Все, что изменилось с момента вашего последнего успешного опроса:
``bash curl -X POST "$API/orders" -H "Authorization: Bearer $TOKEN" \ -d '{"filter":{"company_id":'$SHOP',"updated_from":"2026-09-24T09:00:00Z"},"page":{"number":1,"limit":100}}' # or the same window as CSV curl "$API/orders/export?updated_from=2026-09-24T09:00:00Z" -H "Authorization: Bearer $TOKEN" ``
updated_from это RFC 3339, включительно, и сравнивает с COALESCE(updated_at, created_at): а new order, а stage перемены, а подтверждено payment и возвратить все move Храните время начала каждого Опрос (минус минута перекрытия) как следующий updated_from.
- Разница между опросом и вашим веб-хук feed означает delivery умерший (§5) или ваша конечная точка была ниже, чем окно повторного использования.
5. Гарантии доставки и молчание ignored
Гарантии:
- По крайней мере один раз. А. delivery это row в журнале (
webhook_deliveries, с момента миграции 0106: он пережил перезапуск платформы и был восстановлен 2xx или истощение. поэтому возможны дубликаты - одумываются путемevent_id. - Не заказывать. Доставка различных events В этом же конце находятся независимый; ретритированный старше event может прибыть after новый.
- Успех = HTTP 2xxx внутри 10 s (набрать 5 s, TLS 5 s Внутри этого). Что-нибудь else — 3xx включительно — это провал. Перенаправления не соблюдаются: а 301/302 С вашей конечной точки записывается как окончательный status Покушение.
- петли: до 8 попыток в total, паузы before 2...8 попыток
1s · 5s · 30s · 2m · 10m · 30m · 30m(машиночитаемый в/docs/events.json→delivery.backoff_seconds). После 8-го провала row помечено мертвый (dead_at) и остается в журнале в качестве ответа на "why Не пришло ли оно, не было ли оно вновь. - Получатели являются resolved когда event выстраивается в очередь, Когда не посылается: Последующее редактирование подписки не меняет того, кто получает подписку. event это уже произошло.
- The body хранится и переигрывается байт-за-байт, Таким образом, подпись а повторный повтор соответствует first попытка.
webhook.testявляется синхронной и однократной попыткой; не стоит в очереди.
Молчаливо ignored / Не то, что вы могли бы ожидать:
| Ты делаешь | Что на самом деле происходит |
|---|---|
| Подписаться на An event name Не в белом списке | Отказались создавать (INVALID_EVENT) — не молчать |
Отключить подписку (is_active: false) Пока поставки стоят в очереди | Очередь rows Закрыт как мертвый с subscription is gone or disabled; И ничего не посылается им потом. |
| Ответ 200 медленно (более 10) s) | Считалось неудачей; вы получаете то же самое. delivery Опять же с следующей X-Vizen-Attempt |
| Ожидайте внутреннего price решение одного из order line | Ни в коем случае event: тот line перевозить unit_price, base_unit_price, promotion (для чего-либо) combo) combo_items; тот rule личность за спиной price never Покинет платформу |
Ожидайте чата или журнала order в order.* | Не перевозится — GET /orders/{id} (Журнал) и order Конечные точки сообщений (/docs/orders) |
Ожидать price в promotion.changed / pricing_rule.* | Ни одного: id, kind/action, version Только — цены рассчитываются на одного покупателя (/docs/promotions) |
Подписаться product.updated и редактировать product's запас | Нет. event: движения акций являются регистром, а не product редактировать (/docs/stock) |
Подожди order.status_changed after а payment | Продавец перемещает статусы вручную; надежный сигнал для "paid" это order.paid |
6. События
Список машин с чувствительностью: GET /docs/events.json. Чувствительность определяет, кто может быть подписано (§2)): public и operational (команда's own Работа, нет покупателя data) — любой член; pii, commercial, financial — owner только администратор.
| Событие | Чувствительность | data fields | Избавлялся, когда |
|---|---|---|---|
order.created | pii | order snapshot (§6.1), payment_status: "" | checkout подтвержденный cart входить в order |
order.paid | financial | order snapshot (§6.1) + payment_id, amount (Незначительные единицы), currency, provider, kassa_id, payment_contour (live/test) | тот payment Провайдер подтвердил деньги — единственные надежные "paid" сигнал |
order.refunded | financial | order snapshot (§6.1 с учетом payment_status: "refunded" + amount (Незначительные единицы), currency | тот seller отмеченный payment из одного paid order как refunded |
order.status_changed | pii | order snapshot (§6.1) + old_status, new_status | тот seller перемещать order между статусами (любое направление, включая то же самое) status) |
product.updated | public | product_id, company_id | а product был отредактирован, его kind или дети изменились, или draft содержащая его опубликован |
review.created | public | review_id, company_id, rating, product_id? | Обзор был approved умеренность (появилось) public); уже утвердивший approved Обзор ничего не посылает |
review.submitted | operational | review_id, company_id, rating, product_id? (нет text, нет author) | а buyer Принято для модерации или rejected Один из них fixed И снова послан (/docs/reviews) |
promotion.changed | commercial | promotion_id, company_id, kind, active, name (на publish: promotion_id, company_id только) | а promotion rule был created, редактировать или deleted на live shop, или draft опубликовано — цены могли измениться; нет-product events Вписаться в книгу draft не излучать ничего; откат публикации не излучает event либо |
pricing_rule.changed | commercial | company_id, action, pricing_rule_id?, version? | а quantity-ценовая цена rule был created, редактировать или deleted |
pricing_rule.activated | commercial | company_id, action (activated/deactivated), pricing_rule_id, version | ценообразование rule был включен или off |
key.issued | pii | order_id, company_id, user_id, promotion_id, code, expires_at | Личный купон был выдан в buyer для покупки |
lead.created | pii | lead_id, form_id, form_name, values, page_url?, utm, created_at; test: true на test представление | настоящий form Поступила информация (спам и повторы не испускаются)) |
design.changed | public | company_id, resource_type, resource_id (0 = , default template), slots | тот chrome связывания a resource были изменены |
task.created | operational | задача card: id, sandbox_id, column_id, title, position, assignee_user_id (0 — none), assignee_name, due_at ("" — none), extra (а JSON Объект, а не база64), created_at, updated_at; ids Это цифры, нет description | Задание было created (/docs/tasks) |
task.updated | operational | задача card | задача fields были отредактированы (PATCH /tasks/{id}) |
task.moved | operational | задача card + from_column_id | Задание, изменившее колонку или position (PUT /tasks/{id}/position); cards Удаление колонки ничего не отправляет |
task.deleted | operational | задача card Как это было before удаление | Задание было deleted |
webhook.test | — | webhook_id | Ты давил "test"; Синхронно, не в белом списке |
amount в order.paid и order.refunded в Незначительные единицы (копейки для RUB) — Это payment, Не то order; повсюду else Деньги – это целое единицы shop currency, Как в каталоге. payment (§6.1 является snapshot из payment метод Покупатель выбрал в checkout; payment_id / amount описать payment само собой.
6.1 Order snapshot (order.* тела)
Один строитель (domain.OrderWebhookData) Для всех четырех events, читать after тот Деньги в целых единицах currency если не указано.
| Ключ | Тип | значение |
|---|---|---|
order_id, company_id | int | order и shop ids |
order_no | струна | человек order number; empty на orders сделанный before нумерация |
status, status_code | int, струна | stage: 1 new, 2 processing, 3 done, 4 cancelled |
buyer_id | int | покупатель пользователь id; 0 — гость order |
created_at, updated_at | RFC 3339 UTC | updated_at отсутствующий до first изменение |
items_total | int | товары after скидки |
discount_total | int | скидки applied к товарам |
delivery_cost, payment_cost, total | int | delivery, payment-метод доплаты, подлежащей уплате total |
currency | струна | order currency |
payment_status | струна | "" — нет payment еще; pending, paid, refunded, expired, … как Order.payment_status в /docs/orders |
contact | объект | {name, phone, email} |
comment | струна | покупатель's comment |
address | объект | address Узел как витрина отправил его; отсутствует, когда empty |
utm | объект | Рекламные маркировки order; когда отсутствует когда empty |
delivery, payment | объект | выбранный метод snapshot {id, code, name, type, cost}; отсутствие метода, когда нет |
fields | объект | checkout fields {key: value}; когда отсутствует когда empty |
items[] | массив | product_id, name, key, quantity, unit_price, line_total, data (конфигурационный узел или null); base_unit_price Только когда скидка изменила price; promotion {id, name, amount} Только когда один applied; combo_items Только на a combo line |
old_status, new_status | int | только в order.status_changed |
The snapshot несет персональный data (contact, address, fields) — Отсюда и pii/financial классы и их owner-только подписка (§ 2)).
7. Пределы
| Limit | ценность |
|---|---|
| events за подписку | 1…32 |
| URL длина | ≤ 1024, http(s), public address только |
| secret | 16...64 колесницы; сгенерировано = 32 гекс |
| delivery тайм-аут | 10 s total (набрать 5 s, TLS 5 s) |
| попытки | 8, обратный ход 1s 5s 30s 2m 10m 30m 30m — Также как и числа в /docs/events.json |
| ответ body читать | first 4 KiB, выброшенный |
| рабочий | Опросы в журнале каждые 2 s, 32 поставки по претензиям |
8.Как это было проверено
- Список событий, классы чувствительности и owner-Только ворота: читать из сервер's event реестр; а test Каждый из них будет иметь свои недостатки, если event существует на сервера и отсутствует в этом document, или иным способом round.
- Конверт, заголовки, подпись, тайм-аут, не перенаправлять и приватный-address filter: Читать из delivery рабочий.
- Попытки и отступления: служили цифрами в
/docs/events.jsonот того же самого Константы, которые использует работник. - Семантика журнала (реципиенты) resolved в очереди, body переигрывать байт за байтом,
dead_at): тот delivery Журнал и его работник. datafields: Каждое место, где сервер выдает event (2026-09-10); order snapshot имеет test что не несет в себеpricing_snapshotи опущения optional ключи и интеграция test в которомorder.created→order.paid→order.refundedдойти до реального приемника, подписан (2026-09-24)).- Токен cannot Управление подписками: эти методы не в токене scope Карта, токен получает
PAT_METHOD_NOT_ALLOWED.