Vizen Shop

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-Eventevent name, то же самое, что event в body
X-Vizen-Event-Idто же самое, что event_id
X-Vizen-Delivery-Idid из этого delivery row (event × подписка; отсутствует webhook.test
X-Vizen-Attemptпопытка number, 1-я база; отсутствует на webhook.test
X-Vizen-TimestampКогда эта попытка была предпринята, RFC 3339 UTC
X-Vizen-Signaturesha256=<hex> — См. §4
User-AgentVizen-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 Получить

  1. Читать сырой body, проверить §4.1, отклонить 401 иным образом.
  2. Смотри. event_id В магазине, если вы видите, ответьте 200 Не делай ничего (The Do Nothing) Платформа перезагружается до тех пор, пока не получит 2xx, поэтому дубликат является нормальным.).
  3. упорствовать {event_id, event, created_at, data} и ответить 2xxx немедленно. Делайте реальную работу асинхронно: delivery Тайм-аут - 10 s включая связь и TLS; медленный обработчик зарабатывает повторный, а не more время.
  4. Для 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:

  1. Подписаться (кабинет, owner или администратора) всем четырем order events: order.created, order.status_changed, order.paid, order.refunded. Каждый body Это full snapshot (§6.1 - вставлено order_id, keep тот новейший updated_at (events не заказывается, §5).
  2. Опрос раз в час с персональным токеном (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.

  1. Разница между опросом и вашим веб-хук 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.createdpiiorder snapshot (§6.1), payment_status: ""checkout подтвержденный cart входить в order
order.paidfinancialorder snapshot (§6.1) + payment_id, amount (Незначительные единицы), currency, provider, kassa_id, payment_contour (live/test)тот payment Провайдер подтвердил деньги — единственные надежные "paid" сигнал
order.refundedfinancialorder snapshot (§6.1 с учетом payment_status: "refunded" + amount (Незначительные единицы), currencyтот seller отмеченный payment из одного paid order как refunded
order.status_changedpiiorder snapshot (§6.1) + old_status, new_statusтот seller перемещать order между статусами (любое направление, включая то же самое) status)
product.updatedpublicproduct_id, company_idа product был отредактирован, его kind или дети изменились, или draft содержащая его опубликован
review.createdpublicreview_id, company_id, rating, product_id?Обзор был approved умеренность (появилось) public); уже утвердивший approved Обзор ничего не посылает
review.submittedoperationalreview_id, company_id, rating, product_id? (нет text, нет author)а buyer Принято для модерации или rejected Один из них fixed И снова послан (/docs/reviews)
promotion.changedcommercialpromotion_id, company_id, kind, active, name (на publish: promotion_id, company_id только)а promotion rule был created, редактировать или deleted на live shop, или draft опубликовано — цены могли измениться; нет-product events Вписаться в книгу draft не излучать ничего; откат публикации не излучает event либо
pricing_rule.changedcommercialcompany_id, action, pricing_rule_id?, version?а quantity-ценовая цена rule был created, редактировать или deleted
pricing_rule.activatedcommercialcompany_id, action (activated/deactivated), pricing_rule_id, versionценообразование rule был включен или off
key.issuedpiiorder_id, company_id, user_id, promotion_id, code, expires_atЛичный купон был выдан в buyer для покупки
lead.createdpiilead_id, form_id, form_name, values, page_url?, utm, created_at; test: true на test представлениенастоящий form Поступила информация (спам и повторы не испускаются))
design.changedpubliccompany_id, resource_type, resource_id (0 = , default template), slotsтот chrome связывания a resource были изменены
task.createdoperationalзадача 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.updatedoperationalзадача cardзадача fields были отредактированы (PATCH /tasks/{id})
task.movedoperationalзадача card + from_column_idЗадание, изменившее колонку или position (PUT /tasks/{id}/position); cards Удаление колонки ничего не отправляет
task.deletedoperationalзадача 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_idintorder и shop ids
order_noструначеловек order number; empty на orders сделанный before нумерация
status, status_codeint, струнаstage: 1 new, 2 processing, 3 done, 4 cancelled
buyer_idintпокупатель пользователь id; 0 — гость order
created_at, updated_atRFC 3339 UTCupdated_at отсутствующий до first изменение
items_totalintтовары after скидки
discount_totalintскидки applied к товарам
delivery_cost, payment_cost, totalintdelivery, 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_statusintтолько в order.status_changed

The snapshot несет персональный data (contact, address, fields) — Отсюда и pii/financial классы и их owner-только подписка (§ 2)).

7. Пределы

Limitценность
events за подписку1…32
URL длина≤ 1024, http(s), public address только
secret16...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 Журнал и его работник.
  • data fields: Каждое место, где сервер выдает 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.

Исходник: https://api.vizen.shop/docs/webhooks?full=1

Работает в демо-режиме