«Вебхук не работает» — это два разных отказа под одним названием, и лечатся они по-разному: либо исходящее событие не доходит до вашего обработчика, либо входящий вызов возвращает ошибку вместо данных. Мёртвая синхронизация с учётной системой, бот, переставший писать, вчерашние сделки, не уехавшие в 1С, — всё это один из двух случаев в гриме. Сначала определите, какой именно, дальше идите по проверкам по порядку.

Входящий или исходящий: какая половина сломалась?

Входящий вебхук — это URL с ключом, по которому ваш сервис дёргает портал. Исходящий — наоборот: портал стучится к вам, когда произошло событие. Если в логах видно ушедший запрос и вернувшуюся строку ошибки — сломан входящий, и ошибка сама называет причину. Если в логах пусто — сломан исходящий, и ошибки нет нигде, поэтому диагностика и затягивается. Как создаются оба типа, разобрано в гайде по вебхукам; эта статья — о том, что делать, когда они молчат.

Почему исходящий вебхук не доходит до обработчика?

События не летят из портала прямо на ваш URL. Они складываются в очередь на отдельном сервере доставки, и уже он отправляет POST. Три свойства этого сервера объясняют большую часть тишины. Обработчик обязан быть доступен из интернета: адрес на localhost или во внутренней сети не получит ничего, и фаервол, пускающий только известных партнёров, работает так же. Обработчик обязан отвечать быстро: очередь смотрит на время ответа и обращается к медленным обработчикам реже, растягивая интервал между вызовами, — тяжёлый обработчик превращается в ненадёжный, ни разу не упав явно. И полезная нагрузка нарочно скудная: в основном ID сущности и имя события, поэтому код, ждущий готовых значений полей, читает пустоту. За полями идите отдельным запросом в REST.

То ли это событие, на которое вы подписались?

Второй по частоте случай — исправная подписка не на то событие. События обновления срабатывают на любое изменение сущности, включая изменения, которые делает ваша же автоматизация: со стороны это выглядит как дубли доставок, а иногда закольцовывается. Обработчики, заведённые в разделе «Разработчикам», и обработчики, зарегистрированные приложением через event.bind, лежат в разных списках — та подписка, на которую вы смотрите, может быть не той, что работает. А привязки приложения исчезают, когда приложение удаляют или обновляют. Проверка стоит минуты: выполните действие руками и посмотрите в access-лог, пришёл ли POST.

Обработчик лежал — можно вернуть событие?

Нет, и это то свойство, мимо которого проходит большинство интеграций. Если ваш сервер не ответил или вернул ошибку, сервер очередей запишет неудачу и не отправит событие повторно. Ретраев нет, «мёртвой очереди» нет: десять секунд перезапуска бесшумно стоят всех событий этого окна.

Выходов два. Для входящего направления подписывайтесь на событие как на офлайновое: портал держит события в очереди, а ваш сервис забирает их в своём темпе методом event.offline.get — простой обработчика откладывает доставку, а не уничтожает её. Для вызовов, которые начинаются внутри портала, отправляйте их из бизнес-процесса: «Отказоустойчивый вебхук» повторяет неудачные попытки с заданным интервалом, позволяет объявить, какие коды ответа считать успехом, сообщает число попыток и уведомляет выбранных сотрудников, если доставка так и не удалась.

Почему входящий вебхук возвращает ошибку?

Здесь у вас есть код — читайте его, а не описание рядом. Ошибка прав доступа означает, что ключ создан без нужного методу скоупа, а скоупы фиксируются при создании: ключ надо перевыпустить. Ошибка доступа обычно значит, что с ключом всё в порядке, а с его владельцем нет: вебхук действует от имени создавшего его сотрудника, поэтому уволенный владелец или владелец, не видящий воронку, даёт отказ при полностью валидном токене. QUERY_LIMIT_EXCEEDED — это лимит частоты запросов, а не сломанный ключ: массовые операции упаковывают в batch и повторяют с нарастающей задержкой. Вызов по обычному HTTP вместо HTTPS отклоняется сразу.

Как проверить вебхук, а не гадать?

Логируйте сырое тело запроса до разбора: половина случаев «вебхук сломался» оказывается обработчиком, который POST получил и упал на собственном парсинге. Проверяйте и отправителя — вместе с событием приходит токен приложения, и сверка его с сохранённым значением служит и защитой, и быстрым ответом на вопрос, из портала ли вообще пришёл этот POST.

Обратное направление проверяется изнутри портала: поставьте в процесс «HTTP-запрос» — он отправит запрос на ваш адрес и вернёт тело ответа, код статуса и признак успеха Y/N, то есть покажет, отвечает ли ваш эндпоинт сети портала вообще, а «Извлечь значение из JSON по пути» вытащит из ответа одно поле для читаемой записи в лог. Если процесс, который должен был отправить запрос, вообще не стартовал — след уходит в «не работают роботы».

Итог

Тишина — исходящий, строка ошибки — входящий: одно это разделение экономит большую часть времени. Дальше проектируйте с учётом ограничения, а не вопреки ему: события не переотправляются, поэтому всё, что нельзя терять, идёт через офлайн-подписку на входе и отказоустойчивый вебхук на выходе — оба обычные кубики бизнес-процессов Битрикс24. Остальные кубики — в каталоге Роботеки; нет нужного — опишите задачу, сделаем робота бесплатно и выложим в общую библиотеку.