Исходящие webhook-события
Исходящие webhook-события позволяют внешней системе получать уведомления о важных изменениях в SNABZHENETS+: создании сущностей, обновлении данных, смене статусов, архивировании и тестовой проверке доставки.
Короткий ответ
Подписка API/Webhooks отправляет HTTP-запрос во внешнюю систему, когда в SNABZHENETS+ происходит выбранное событие. Имя события передаётся в поле event тела webhook и в заголовке X-Snabplus-Event.
В подписке нужно выбрать хотя бы одно событие из публичного каталога. Пустой список событий не используется как режим «все события» и не принимается при создании или обновлении подписки.
Где получить каталог
Актуальный список публичных событий доступен через API:
GET /api/v1/integration-api/metaСобытия находятся в поле events ответа.
GET /api/v1/integrations/api-webhooks/installations/:id/consoleСобытия находятся в поле eventCatalog ответа.
Для настройки подписки используйте поле subscriptionEventCatalog: в нём только бизнес-события, доступные для выбора. integration.test_event не входит в этот список и отправляется только ручной кнопкой проверки подписки.
Настройка через API
Исходящие подписки можно создавать и обслуживать через Integration API:
GET /api/v1/integration-api/webhook
POST /api/v1/integration-api/webhook
PUT /api/v1/integration-api/webhook/:id
DELETE /api/v1/integration-api/webhook/:id
POST /api/v1/integration-api/webhook/:id/testВсе bodies и responses — JSON. Для чтения нужен scope webhooks.read или integration_api.read; для создания, изменения и теста — webhooks.write или integration_api.write; для отключения — webhooks.delete или integration_api.delete.
Создание принимает массив. Каждый элемент — отдельный webhook на одно событие и один URL:
POST /api/v1/integration-api/webhook
Content-Type: application/json
Authorization: Bearer spk_...[
{
"name": "ERP procurement status",
"event": "integration_api.procurement.status_changed",
"url": "https://erp.example.com/hooks/snabzhenets",
"isActive": true
}
]Ответ:
{
"items": [
{
"id": "hook_id",
"name": "ERP procurement status",
"event": "integration_api.procurement.status_changed",
"url": "https://erp.example.com/hooks/snabzhenets",
"isActive": true,
"successCount": 0,
"failureCount": 0,
"lastDeliveredAt": null,
"lastErrorAt": null,
"lastErrorMessage": null,
"createdAt": "2026-06-09T09:00:00.000Z",
"updatedAt": "2026-06-09T09:00:00.000Z",
"secretPlaintext": "one-time-secret"
}
]
}GET /api/v1/integration-api/webhook возвращает те же параметры без secretPlaintext. DELETE не удаляет запись физически, а переводит webhook в isActive: false.
Пример изменения подписки:
PUT /api/v1/integration-api/webhook/hook_id
Content-Type: application/json
Authorization: Bearer spk_...{
"name": "ERP procurement updates",
"event": "integration_api.procurement.updated",
"url": "https://erp.example.com/hooks/procurements",
"isActive": true
}Пример отключения:
DELETE /api/v1/integration-api/webhook/hook_id
Authorization: Bearer spk_...Пример тестовой доставки:
POST /api/v1/integration-api/webhook/hook_id/test
Authorization: Bearer spk_...Тестовая доставка отправляет событие integration.test_event на URL подписки и возвращает результат попытки в JSON. Боевой секрет подписи в list/update/delete/test ответах не возвращается.
Сущности и события
| Сущность | Системная сущность | Событие | Что означает |
|---|---|---|---|
| Закупка | procurement | integration_api.procurement.created | Создана новая закупка. |
| Закупка | procurement | integration_api.procurement.updated | Изменены данные закупки без отдельного события смены статуса. |
| Закупка | procurement | integration_api.procurement.status_changed | У закупки изменился статус. |
| Закупка | procurement | integration_api.procurement.deleted | Закупка удалена или архивирована. |
| Номенклатура | catalog_item | integration_api.catalog_item.created | Создана новая позиция номенклатуры. |
| Номенклатура | catalog_item | integration_api.catalog_item.updated | Изменены данные позиции номенклатуры. |
| Номенклатура | catalog_item | integration_api.catalog_item.deleted | Позиция номенклатуры удалена или архивирована. |
| Контрагент | counterparty | integration_api.counterparty.created | Создан новый контрагент. |
| Контрагент | counterparty | integration_api.counterparty.updated | Изменены данные контрагента. |
| Контрагент | counterparty | integration_api.counterparty.status_changed | У контрагента изменился статус. |
| Контрагент | counterparty | integration_api.counterparty.deleted | Контрагент удалён или архивирован. |
| Склад | warehouse | integration_api.warehouse.created | Создан новый склад. |
| Склад | warehouse | integration_api.warehouse.updated | Изменены данные склада. |
| Склад | warehouse | integration_api.warehouse.deleted | Склад удалён или архивирован. |
| Прайс-лист | price_list | integration_api.price_list.created | Создан новый прайс-лист. |
| Прайс-лист | price_list | integration_api.price_list.updated | Изменены данные прайс-листа. |
| Прайс-лист | price_list | integration_api.price_list.status_changed | У прайс-листа изменился статус. |
| Прайс-лист | price_list | integration_api.price_list.deleted | Прайс-лист удалён или архивирован. |
| Счёт | invoice | integration_api.invoice.created | Создан новый счёт. |
| Счёт | invoice | integration_api.invoice.updated | Изменены данные счёта. |
| Счёт | invoice | integration_api.invoice.status_changed | У счёта изменился статус. |
| Счёт | invoice | integration_api.invoice.deleted | Счёт удалён или архивирован. |
| Тест интеграции | integration | integration.test_event | Техническое тестовое событие для проверки подписки, URL, заголовков и подписи. |
Типы событий
| Тип | Суффикс | Когда отправляется |
|---|---|---|
| Создание | created | В SNABZHENETS+ появилась новая сущность. |
| Обновление | updated | Изменились поля существующей сущности. |
| Смена статуса | status_changed | Изменился статус сущности, и внешняя система должна отдельно отреагировать на переход. |
| Удаление или архивирование | deleted | Сущность стала неактивной, удалена или архивирована. Этот тип есть только у архивируемых сущностей. |
| Тестовое событие | test_event | Пользователь вручную проверяет webhook-подписку без изменения бизнес-данных. |
Payload
Тело исходящего webhook остаётся стабильным:
{
"event": "integration_api.procurement.status_changed",
"eventId": "audit-log-uuid",
"schemaVersion": "1.0",
"resourceVersion": 7,
"correlationId": "erp-order-1001",
"tenantId": "tenant-uuid",
"entity": {
"type": "procurement",
"id": "entity-uuid"
},
"occurredAt": "2026-05-28T10:00:00.000Z",
"resource": {},
"data": {
"changes": {},
"snapshot": {},
"metadata": {}
},
"links": [
{
"externalSource": "erp",
"externalId": "PO-1001",
"entityType": "procurement",
"entityId": "entity-uuid"
}
]
}Для документов и логистики доступны события:
integration_api.document_generation.completedintegration_api.document_generation.failedintegration_api.delivery_trip.createdintegration_api.delivery_trip.updatedintegration_api.delivery_trip.status_changedintegration_api.delivery_trip.stop_status_changed
resourceVersion позволяет сохранить порядок обновлений одной сущности, а eventId — безопасно удалить дубль. Постоянные ссылки на файлы в payload не передаются.
Заголовки и подпись
Каждый исходящий webhook подписывается HMAC-SHA256:
X-Snabplus-Event: integration_api.procurement.status_changed
X-Snabplus-Event-Id: audit-log-uuid
X-Snabplus-Timestamp: 1760000000000
X-Snabplus-Signature: sha256=<hex>Подпись считается по строке:
{timestamp}.{rawBody}Внешняя система должна проверить подпись до обработки события.
Повторы доставки
SNABZHENETS+ повторяет доставку при сетевой ошибке, 408, 429 и ответах 5xx. Каждая попытка видна в журнале доставки API/Webhooks. Worker атомарно захватывает доставку в lease, поэтому несколько экземпляров не отправляют её одновременно. DNS и публичность адреса проверяются заново непосредственно перед каждой попыткой.
Аудит восстанавливается через durable cursor: события, накопившиеся во время остановки worker, будут поставлены в очередь после запуска, а уникальная пара subscriptionId + eventId защищает от повторного создания доставки.