Когда webhook так и не приходит

Разбирайтесь по порядку - лог доставки, название события, файрвол, подпись. Вкладка Deliveries сразу показывает, отправил ли Didit событие, что делит проблему пополам.

Short answer

Начните со вкладки Deliveries у нужного назначения. Если Didit вообще не пытался доставить событие - проблема в подписке или в самом назначении. Если попытка была и она провалилась, код ответа подскажет, в чём дело: 404 означает, что ваш маршрут был недоступен по этому URL, а несовпадение подписи означает, что вы захэшировали не те байты.

#Шаг 1: пытался ли Didit отправить событие?

Откройте назначение в разделе API & Webhooks и посмотрите на вкладку Deliveries. Каждая попытка логируется отдельно, вместе с полученным ответом.

Страница webhooks в консоли Didit со статусом доставки по каждому назначению
  1. Last sent показывает, пытался ли Didit вообще что-то отправить.
  2. View stats показывает попытки и сбои для этого назначения.
  3. Тестовая доставка отделяет проблему вашего эндпоинта от проблемы самого события.
  4. Назначение, которое постоянно падает, можно отключить, пока вы его чините.
На этой странице есть всё, чтобы отличить отсутствующее событие от неработающего эндпоинта.

Эта единственная проверка делит проблему пополам:

  • Попытка не залогирована → событие для этого назначения вообще не сгенерировалось. Переходите к шагу 2.
  • Попытка залогирована, неудачно → Didit отправил событие, а ваша сторона его отклонила или не получила. Переходите к шагу 3.
  • Попытка залогирована, 2xx → доставка прошла успешно. Проблема внутри вашего обработчика, а не в доставке.

#Шаг 2: попытка не залогирована

По убыванию вероятности:

  1. Событие не подписано. Подстановочного знака нет - каждое семейство событий нужно перечислять явно. А название, которого не существует (session.status.updated, kyc.completed), молча ничего не получает. Сверьтесь со списком событий.
  2. Не то приложение. Назначения принадлежат приложению. Если ваши сессии выполняются в другом приложении, чем назначение, событие туда никогда не дойдёт. Это самая частая причина, когда всё выглядит настроенным правильно.
  3. На самом деле ничего не изменилось. Webhook срабатывает при изменении. Сессия, которая не сдвинулась с места, или повторный AML-скрининг, не нашедший ничего выше порога, закономерно не порождают событие.
  4. Статус, которого вы ждёте, ещё не наступил. Сессия в статусе In Progress ещё не завершена. См. когда сессия так и не завершается.

#Шаг 3: доставка была предпринята и провалилась

404 означает, что запрос дошёл до чего-то, у чего нет вашего маршрута. Проверьте:

  • Точный путь, включая завершающий слэш. Фреймворк, который редиректит /hook на /hook/, может превратить рабочий эндпоинт в 404 или потерянное тело запроса.
  • Доступен ли URL публично. Локальный или staging-хост, недоступный из интернета, падает именно так.
  • Не отправляет ли этот путь куда-то ещё прокси, балансировщик нагрузки или роутер на основе пути перед вашим приложением.

5xx означает, что ваш обработчик выбросил исключение. Логируйте сырое тело запроса до парсинга, чтобы видеть, что он на самом деле получил.

Таймаут означает, что вы не ответили достаточно быстро. Сначала верните 2xx, а обработку выполняйте после.

Вообще ничего / соединение отклонено означает, что ваш периметр заблокировал запрос. Didit доставляет события со статического IP 18.203.201.92 с user agent DiditWebhook/2.0. Если вы за Cloudflare или WAF с политикой запрета по умолчанию, разрешите этот IP для принимающего хоста.

#Классика: автоматическая доставка падает с 404, а Resend работает

Этот случай встречается достаточно часто, чтобы дать ему имя. Автоматическая доставка падает с 404, а затем нажатие Resend на том же событии проходит успешно.

Такое сочетание означает, что и содержимое, и ваш эндпоинт в порядке - значит, разница в тайминге или пути, а не в содержимом. Проверьте:

  • Деплой или рестарт в момент исходной доставки. Resend срабатывает позже, когда приложение уже снова поднято.
  • Холодный старт, превысивший таймаут вашей платформы - типично для serverless с медленным первым вызовом.
  • Маршрутизацию по пути, которая изменилась между двумя попытками, или правило, которое совпадает не для всех запросов.
  • Ограничение частоты запросов или защиту от ботов на вашем периметре, которая пропустила ручной повтор, потому что он пришёл один, а не пачкой.

Во вкладке Deliveries есть обе попытки с временными метками - сравните их со своими логами деплоев и ошибок за эту минуту.

#Проверка подписи не проходит

Почти всегда одна из трёх причин:

  1. Вы захэшировали пересериализованный JSON. Считайте HMAC от сырых байтов тела запроса ровно в том виде, в котором оно получено. Разбор и повторная сериализация меняют пробелы и порядок ключей, и подпись перестаёт совпадать. Большинству фреймворков нужна явная настройка, чтобы отдать сырое тело.
  2. Неверный секрет. Секрет подписи задаётся на каждое назначение отдельно, и это не ваш API-ключ. У двух назначений - два разных секрета.
  3. Неверное предположение о кодировке. Если вы не уверены, используется ли секрет как обычная строка или его нужно предварительно декодировать, не гадайте - следуйте справке по проверке подписи буквально и логируйте сырое тело во время отладки, чтобы можно было сравнить.
Important

Никогда не "чините" несовпадение подписи, просто отключив проверку. Непроверяемый эндпоинт webhook примет поддельное подтверждение от кого угодно, кто найдёт этот URL, - и отладочный обходной путь превращается в способ захвата аккаунта.

#Два повтора - это не очередь

При 5xx, 404, таймауте или обрыве соединения Didit повторяет попытку дважды - примерно через 1 минуту, а затем ещё через 4 минуты - и после этого доставку прекращает. Если ваш эндпоинт был недоступен дольше этого времени, эти события потеряны безвозвратно.

Постройте механизм сверки: при старте опрашивайте endpoint решений по каждой сессии, для которой у вас нет финального статуса. Считайте webhook быстрым путём, а опрос - подстраховкой.

#Тестирование без реальных верификаций

Try Webhook на странице назначения отправляет полностью сформированное событие любого нужного вам вида - approved, declined, in review, KYB, entity, transaction. С его помощью можно убедиться, что ваш эндпоинт, проверка подписи и обработчик работают, прежде чем от них будет зависеть настоящая сессия.

Sandbox дополняет это с другой стороны: sandbox-сессия отправляет настоящие webhook с "environment": "sandbox", поэтому вы можете бесплатно прогнать весь путь целиком. См. тестирование в sandbox.