Как тестировать вебхуки в ForTunnels

Платёж прошёл, а заказ остался неоплаченным. Где искать ошибку: платёжный сервис не отправил webhook, запрос потерялся по дороге или обработчик не понял его содержимое? Логи приложения помогут, если запрос до него дошёл.

Проверим это на ForTunnels: примем запрос в облачный Inbox, вернём отправителю ошибку, а затем подключим локальный обработчик через HTTP-туннель.

Сначала ловим запрос

Форма создания адреса
Форма создания адреса
В карточке есть полный URL и кнопка «Копировать»
В карточке есть полный URL и кнопка «Копировать»

В личном кабинете открываем «Inbox webhook» и нажимаем «Создать адрес». Вводим название, например «Хабр: тест оплаты», и путь /webhooks. Краткое описание можно оставить пустым. После создания адрес работает в режиме «Только хранение»: запросы принимает сам сервис, запускать локальное приложение или клиент туннеля пока не нужно.

Форма создания адреса. Все скриншоты сняты в локальной версии 2 октября 2026 года на демонстрационных данных.

В карточке есть полный URL и кнопка «Копировать». На скриншоте адрес локального стенда; для внешнего отправителя нужен HTTPS-URL из вашего кабинета.

Перед отправкой на вкладке «Состояние» находим кнопку «Открыть инспектор» под чеклистом настройки. В инспекторе переходим в «Настройки», включаем «Полный захват тел» и сохраняем изменение. Эта возможность должна быть доступна на тарифе. Без неё в журнале останутся метаданные, но посмотреть JSON не получится. Ручные тесты, mock-ответы и Replay тоже зависят от тарифа.

Первый запрос отправим через curl. В переменную вставляем скопированный URL целиком, вместе с /webhooks:

WEBHOOK_URL='ВСТАВЬТЕ_HTTPS_URL_ИЗ_КАБИНЕТА' curl -i "$WEBHOOK_URL" \ -H 'Content-Type: application/json' \ -H 'X-Webhook-Source: habr-demo' \ --data '{ "id": "evt_test_001", "type": "payment.succeeded", "data": { "order_id": "order_42", "amount": 1500 } }'

Inbox ответит 200 OK, а во вкладке «События» появится запись. Открываем её и сверяем метод, путь, Content-Type и тело запроса. Уже здесь можно заметить, что отправитель использует другой путь, передаёт форму вместо JSON или кладёт идентификатор заказа не в то поле, где его ищет обработчик.

В деталях открыта вкладка «Запрос», а тело показано в режиме JSON. Это данные из команды выше: событие evt_test_001 и заказ order_42
В деталях открыта вкладка «Запрос», а тело показано в режиме JSON. Это данные из команды выше: событие evt_test_001 и заказ order_42

Теперь указываем этот URL в тестовом окружении платёжного сервиса и вызываем тестовое событие уже оттуда. Запрос через curl проверил приём на нашей стороне; доставку от провайдера ещё предстоит проверить. Формат JSON у него будет свой.

Ответ 200 OK от Inbox означает, что сервис принял запрос. В режиме «Только хранение» он не меняет статус заказа и не выполняет нашу бизнес-логику. Это проверим позже, на своём обработчике.

Возвращаем 503 и проверяем повторную доставку

Вернёмся в карточку адреса. На вкладке «Доставка и mock» выбираем «Mock ответы» и нажимаем пресет 503 Unavailable. Появится включённое правило для всех запросов. Ниже находится «Fallback-ответ»: он применяется, когда ни одно правило не подошло.

Правило <a href="/tag/0">#0</a> включено → 503 срабатывает для всех запросов. Пока оно включено, до fallback со статусом 200 дело не дойдёт.
Правило #0 включено → 503 срабатывает для всех запросов. Пока оно включено, до fallback со статусом 200 дело не дойдёт.

Повторяем ту же команду curl. Теперь получаем 503 Service Unavailable с телом {"error":"unavailable"}. В инспекторе открываем новое событие и переключаем детали на «Ответ», чтобы увидеть статус и тело, которые получил отправитель.

В журнале два запроса, отправленных вручную: до включения mock-правила и после. Справа виден ответ 503 с телом {"error":"unavailable"}
В журнале два запроса, отправленных вручную: до включения mock-правила и после. Справа виден ответ 503 с телом {"error":"unavailable"}

Чтобы проверить автоматические повторы, снова вызываем тестовое событие у провайдера. Если он повторяет доставку после 503, в журнале появятся новые запросы. Сравниваем идентификаторы событий, содержимое и интервалы между попытками. Затем выключаем правило 503: при fallback со статусом 200 и отсутствии других подходящих правил Inbox начнёт отвечать успешно. Проверяем, прекратил ли отправитель повторы. Их расписание и условия зависят от провайдера; обычная команда curl из примера сама запрос не повторяет.

Для проверки таймаута можно задать задержку ответа, например 3000 мс. Если используем поле «Задержка, мс» в fallback, сначала выключаем правило для всех запросов, иначе fallback не сработает. В mock-режиме допустима задержка до 10 секунд. Успеет ли клиент дождаться ответа, зависит от его настроек.

Подключаем локальный обработчик

Допустим, обработчик уже запущен на localhost:8000/webhooks. Подключим его через обычный HTTP-туннель. Устанавливаем CLI, создаём токен в кабинете и сохраняем его в локальной конфигурации:

fortunnels config add-authtoken YOUR_FORTUNNELS_TOKEN fortunnels config check fortunnels http 8000

В настройках провайдера заменяем адрес Inbox на HTTPS-URL нового туннеля с путём /webhooks. Оставляем клиент запущенным. Теперь запрос дойдёт до локального приложения: можно поставить точку останова в обработчике и проследить, почему заказ не меняет статус.

Если обработчик вернул ошибку, исправляем код, открываем событие в инспекторе туннеля и нажимаем «Replay request». Запрос снова уйдёт на цель этого туннеля. Полностью сохранённое тело подставится автоматически; если оно отсутствует или обрезано, задаём его вручную. Учётные данные, cookies и служебные заголовки при повторе удаляются. Поэтому Replay подходит для отладки обработки запроса, а проверку подписи провайдера нужно учитывать отдельно.

Проверяем дубли и ошибки в данных

В деталях события есть кнопка «Сохранить как шаблон». Сохраняем запрос и открываем его во вкладке «Тест». На его основе можно подготовить варианты без order_id, с неизвестным типом события и с повторяющимся id.

Дубль особенно полезно проверить для платежей: два вызова с одним событием не должны дважды менять баланс или создавать две отгрузки. Одного ответа 200 здесь недостаточно. После повторной отправки смотрим, что произошло с заказом и записями в базе приложения.