Событие регистрации своих сущностей веб-хуков
[OnBuildWebhookEntityList] Своя сущность в веб-хуках модуля
На более ранних версиях событие не вызывается, а веб-хук по сущности, которую модуль не обслуживает, отбрасывается молча.
Модуль сам раскладывает по каталогу Битрикс шесть типов сущностей МойСклад: product, variant, bundle, service, productfolder и specialpricediscount. Событие позволяет вашему коду объявить свою сущность — например партию (consignment), контрагента (counterparty) или заказ покупателя (customerorder) — или встроиться в обработку штатной.
Объявление — это запись в реестре модуля: тип сущности, действия, ваш обработчик и режим. Дальше модуль:
- подписывается в МойСклад на веб-хуки по объявленным действиям — сущность встаёт на вкладке «Веб-хуки» отдельным блоком с подписью «Обслуживает внешний обработчик:
<обработчик>(<режим>)»; - вызывает ваш обработчик на каждый пришедший веб-хук по этой сущности — вместо своей обработки, до неё или после неё, в зависимости от режима.
Главное отличие от старого пути через OnBeforeWebHookOptionsBuild: объявленную сущность модуль не перехватит при обновлении. Если в следующей версии модуль научится обслуживать ту же сущность сам, в режиме replace веб-хуки по ней всё равно пойдут только в ваш обработчик.
Режимы
| Режим | Что происходит с веб-хуком |
|---|---|
replace (по умолчанию) | Веб-хук получает только ваш обработчик. Модуль сущность не трогает, даже если это одна из шести штатных. Штатную сущность в этом режиме модуль убирает из своих подписок — остаются только объявленные вами действия |
before | Сначала ваш обработчик, затем — штатная обработка модуля. Обработчик может отменить обработку модулем, вернув EventResult с типом ERROR |
after | Сначала штатная обработка модуля, в том числе события OnWebhookUpdate и OnWebhookSkipped, затем — ваш обработчик с элементом, который модуль обработал |
Для сущности, которую модуль сам не обслуживает, режимы before и after ничем не отличаются от replace: обработчик вызывается один раз, параметр phase равен объявленному режиму, элементов в параметрах нет.
Режим задаётся на сущность целиком, а не на отдельное действие.
Правила реестра
- У сущности один владелец. Если одну сущность объявили два обработчика, остаётся объявление того, кто вызвался первым; второе отбрасывается с записью в лог модуля.
- Битое объявление отбрасывается целиком с записью в лог модуля, исключение не бросается. Причины: тип сущности не из строчных латинских букв, объявление не массив,
actionsпуст или содержит действие кромеCREATE,UPDATE,DELETE,handlerне указан или его нельзя вызвать,modeне из трёх допустимых. - Обработчик получает только объявленные действия. Если сущность объявлена с
['CREATE', 'UPDATE'], а пришёлDELETE: в режимеreplaceвеб-хук не обрабатывает никто (строка в лог модуля), в режимахbeforeиafterштатную сущность модуль обработает сам, без вашего обработчика. - Реестр один на все профили обмена. Профиль в параметрах не передаётся — см. шаг 3 во вкладке «Примеры использования».
- Событие вызывается один раз за хит, дальше модуль берёт результат из своего кеша. Вызывается оно на странице настроек и на каждом входящем веб-хуке, поэтому обработчик события должен только вернуть описание: без запросов к API МойСклад и тяжёлых выборок.
Что модуль гарантирует к моменту вызова обработчика
Обработчик вызывается не на сыром теле запроса. К этой точке модуль уже:
- проверил соль в адресе веб-хука (
webhook_url_salt) сравнением за постоянное время; - убедился, что обмен включён, и выбрал профиль обмена по
profile_id; - нормализовал домен в
meta->href; - проверил
hrefна принадлежность эндпоинту МойСклад — чужой адрес в теле веб-хука до обработчика не доходит; - убедился, что действие входит в
CREATE,UPDATE,DELETEи в объявленные вамиactions; - отсёк повтор того же события в пределах времени дедупликации (
cache_webhook_time, по умолчанию 5 секунд).
Кроме фазы after по штатной сущности, у обработчика нет объекта МойСклад — модуль не знает, как устроена ваша сущность, и в API за ней не обращается. Вам приходит href; запрос по нему — на вашей стороне.
Когда обработчик не вызывается
| Случай | Почему |
|---|---|
Действие не входит в actions объявления | См. «Правила реестра» выше |
Действие вне CREATE / UPDATE / DELETE | Модуль отбрасывает такой веб-хук раньше развилки по типу |
href не принадлежит API МойСклад | Отсекается защитой от подстановки чужого адреса |
Повтор того же события в пределах cache_webhook_time | Дедупликация: второй раз обработчик не вызывается |
Событие не попало в первые webhook_limit_count событий запроса | Модуль обрабатывает только начало пачки (настройка «Количество событий за раз» на вкладке «Веб-хуки»). Остаток пишется в лог и отдаётся событием OnWebhookEventsDropped |
| Обмен выключен или соль не совпала | Приём обрывается до разбора тела |
Исключение из вашего обработчика модуль ловит и пишет в лог модуля строкой с именем обработчика, сущностью, действием и фазой — остальные события пачки обрабатываются дальше. В режиме before исключение обработку модулем не отменяет.
- Описание события
- Пример вызова события
- Примеры использования
Параметры события
Событие не передаёт параметров.
Возвращаемое значение
EventResult::SUCCESS с ключом entities — массивом объявлений, где ключ — тип сущности МойСклад:
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS, [
'entities' => [
'consignment' => [
'actions' => ['CREATE', 'UPDATE', 'DELETE'],
'handler' => ['\My\Module\ConsignmentHook', 'handle'],
'mode' => 'replace',
],
],
]);
Поля объявления
| Поле | Обязательное | Тип | Описание |
|---|---|---|---|
| ключ массива | да | string | Тип сущности МойСклад (meta->type веб-хука): только строчные латинские буквы |
actions | да | string[] | Действия, на которые подписаться и которые отдавать обработчику: CREATE, UPDATE, DELETE |
handler | да | callable | Обработчик веб-хука: имя функции, [класс, метод] или замыкание. Должен быть вызываемым в момент объявления |
mode | нет | string | replace (по умолчанию), before или after |
Параметры обработчика веб-хука
Обработчик вызывается как обычный обработчик события Битрикс — с одним объектом \Bitrix\Main\Event (тип OnWebhookCustomEntity). Через EventManager этот объект не отправляется: подписаться на OnWebhookCustomEntity через addEventHandler нельзя, обработчик вызывается только из реестра.
| Параметр | Тип | Описание |
|---|---|---|
hook | object | Объект события из тела веб-хука МойСклад, с уже нормализованным meta->href |
entity | string | Тип сущности из объявления |
action | string | Действие веб-хука: CREATE, UPDATE или DELETE |
href | string | Ссылка на сущность в API МойСклад — то же, что hook->meta->href, строкой |
mode | string | Режим из объявления |
phase | string | Момент вызова: replace, before или after |
bx_element | array|null | Только в фазе after: элемент Битрикс, тот же, что уходит в OnWebhookUpdate. null, если модуль элемент не загрузил: действие DELETE, выключен импорт групп, модификации грузятся агентом, элемента нет в каталоге или сущность не своя |
ms_element | object|null | Только в фазе after: объект МойСклад, загруженный модулем. null в тех же случаях |
Возвращаемое значение обработчика
Читается только в фазе before: EventResult с типом ERROR отменяет обработку веб-хука модулем. Любой другой ответ, включая null, ничего не отменяет. В остальных фазах результат не читается.
//Пример кода нужно вставить в файл init.php
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'rbs.moyskladstocks',
'OnBuildWebhookEntityList',
'OnBuildWebhookEntityListHandler'
);
function OnBuildWebhookEntityListHandler(\Bitrix\Main\Event $event)
{
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS, [
'entities' => [
'consignment' => [
'actions' => ['CREATE', 'UPDATE', 'DELETE'],
'handler' => 'ConsignmentWebhookHandler',
'mode' => 'replace',
],
],
]);
}
function ConsignmentWebhookHandler(\Bitrix\Main\Event $event)
{
$eventParams = $event->getParameters();
$action = $eventParams['action']; // CREATE, UPDATE или DELETE
$href = $eventParams['href']; // ссылка на сущность в API МойСклад
if ($action === 'DELETE') {
// Сущности в МойСклад уже нет — ходить за ней бессмысленно,
// работайте по идентификатору из href
YourConsignmentStore::dropByHref($href);
return;
}
// CREATE и UPDATE: объект забираете сами
#WORK_AREA#
}
Шаг 1. Объявить сущность и сохранить настройки веб-хуков
После объявления сущность появляется на вкладке «Веб-хуки» отдельным блоком. Подписка в МойСклад создаётся так же, как для штатных сущностей, — из настроек модуля. Пока подписки нет, МойСклад веб-хуки по сущности не шлёт.
Шаг 2. Забрать сущность из МойСклад по href
function ConsignmentWebhookHandler(\Bitrix\Main\Event $event)
{
$eventParams = $event->getParameters();
if ($eventParams['action'] === 'DELETE') {
return;
}
// ApiNew::get() принимает как относительный путь ('/entity/consignment/...'),
// так и href целиком. Токен, лимиты запросов и профиль обмена
// он берёт из настроек модуля сам
$msObject = \Rbs\MoyskladStocks\ApiNew::get($eventParams['href']);
if (!is_object($msObject) || \Rbs\MoyskladStocks\Utils::has_errors($msObject)) {
return;
}
YourConsignmentStore::upsert($msObject);
}
Шаг 3. Узнать профиль обмена, если их несколько
В параметрах профиль не передаётся — реестр один на все профили. Номер активного профиля берётся у модуля:
$profileId = (int)\Rbs\MoyskladStocks\Config::getProfileId();
Встроиться в обработку штатной сущности: не пускать модуль к архивным товарам
function OnBuildWebhookEntityListHandler(\Bitrix\Main\Event $event)
{
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::SUCCESS, [
'entities' => [
'product' => [
'actions' => ['UPDATE'],
'handler' => 'ProductBeforeWebhookHandler',
'mode' => 'before',
],
],
]);
}
function ProductBeforeWebhookHandler(\Bitrix\Main\Event $event)
{
$href = $event->getParameter('href');
if (YourRules::isFrozen($href)) {
// Модуль этот веб-хук обрабатывать не будет
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::ERROR);
}
return null;
}
CREATE и DELETE по товарам здесь не объявлены — их модуль обрабатывает сам, без вызова обработчика.
Дообработать элемент после модуля
'product' => [
'actions' => ['CREATE', 'UPDATE'],
'handler' => function (\Bitrix\Main\Event $event) {
$bxElement = $event->getParameter('bx_element');
$msElement = $event->getParameter('ms_element');
if ($bxElement === null || $msElement === null) {
return; // модуль элемент не загрузил
}
#WORK_AREA#
},
'mode' => 'after',
],
МойСклад не гарантирует доставку, а модуль обрабатывает только первые webhook_limit_count событий запроса — остаток приходит в OnWebhookEventsDropped. Стройте на веб-хуках быструю реакцию, а полноту данных обеспечивайте отдельным периодическим проходом.
Обновление на 3.9.0: что делать владельцу существующей доработки
| Как устроена ваша доработка | Что делать при обновлении |
|---|---|
Обработчики OnWebhookUpdate, OnWebhookSkipped, OnBeforeDeleteItem и события импорта | Ничего. Имена, параметры и момент вызова не изменились |
| Собственный приёмник веб-хуков на своём адресе, мимо модуля | Ничего. Модуль в этой цепочке не участвует |
Подписка на свою сущность через OnBeforeWebHookOptionsBuild | Подписка работает как раньше, но веб-хуки по сущности, которую не обслуживают ни модуль, ни реестр, модуль по-прежнему не обрабатывает — теперь он пишет о каждом таком веб-хуке строку в лог. Чтобы получать их, объявите сущность в OnBuildWebhookEntityList |
| В коде модуля правился список обслуживаемых типов | Правка будет затёрта обновлением. Перенесите логику в обработчик из реестра: он переживёт все следующие обновления |
В версиях до 3.8.0 веб-хук с действием DELETE по любому типу сущности шёл в штатное удаление: модуль искал в каталоге элемент, у которого XML_ID совпадает с идентификатором из href, и удалял или деактивировал его. Это работало и для сущностей, которые модуль не обслуживает, — и было закрыто в 3.8.0 как уязвимость: тело веб-хука недоверенное, а совпадение идентификатора ничего не подтверждает.
Если ваша доработка опиралась на этот побочный эффект, после обновления он не вернётся. Снимать элементы каталога по своей сущности теперь нужно самостоятельно, из обработчика реестра с действием DELETE.