Отладка вебхука на ноутбуке обычно требует туннеля: адрес меняется при каждом перезапуске, провайдера приходится перенастраивать, а половина событий срабатывает только в продакшене. Постоянный catch-all URL в облаке решает это иначе: провайдер всегда обращается по одному адресу, а вы открываете перехваченный запрос когда удобно.
Почему catch-all URL удобнее туннеля
- Постоянный адрес — провайдер настраивается один раз, вместе с ретраями и тестовыми событиями.
- Никаких портов на вашей машине — нечего открывать и не нужно держать ноутбук включённым.
- Полная история — можно сравнить последние десять попыток одного и того же события, а не только пойманную вживую.
- Можно поделиться — read-only ссылка даст коллеге посмотреть payload без доступа к вашему аккаунту.
Шаг 1. Создайте URL вебхука
Создайте вебхук и скопируйте выданный URL. Принимается любой метод и любой content type: сохраняются метод, полный URL, query-строка, все заголовки, тело, IP клиента, user-agent и время.
Шаг 2. Направьте на него провайдера
Добавьте URL как endpoint в панели провайдера и вызовите событие:
- Stripe — Developers → Webhooks → Add endpoint; используйте Send test webhook или Resend на реальном событии.
- GitHub — Repository → Settings → Webhooks; кнопка Redeliver повторяет любую прошлую доставку.
- Telegram / Slack / свой сервис — вызовите сами:
curl -X POST https://your-sandbox-host/w/YOUR_TOKEN \
-H "Content-Type: application/json" \
-H "X-Custom-Signature: test" \
-d '{"event":"payment.succeeded","id":42}'
Шаг 3. Посмотрите, что реально пришло
Откройте детали запроса: query-строка таблицей, заголовки и тело в отформатированном виде, если это JSON. Именно здесь становится видно большинство проблем интеграции:
- провайдер прислал
application/x-www-form-urlencoded, а парсер ждёт JSON;
- заголовок подписи отсутствует, потому что endpoint в тестовом режиме;
- полезная нагрузка вложена на уровень глубже, чем сказано в документации;
- событие — это повтор уже обработанного события.
Учтите, что подпись считается по сырому телу, ровно по тем байтам, что пришли. Храните и сравнивайте raw body, а не пересобранный JSON.
Шаг 4. Воспроизведите запрос локально
Скопируйте тело и заголовки из перехвата и отправьте тот же запрос на свой dev-сервер. Поскольку виден весь исходный запрос, падающий локальный обработчик воспроизводится без догадок и без ожидания провайдера.
Подсказки
- Если провайдер делает ретраи, группируйте запросы по id события (Stripe передаёт его в заголовке) — так видно дубликаты.
- Дайте URL коллеге только на чтение: создайте ссылку и отзовите её, когда отладка закончится.
- Для автоматизации ссылка умеет отдавать JSON — см. Проверка писем с помощью ИИ-ассистента.
Что дальше
Как тестировать вебхук на localhost?
Либо пусть провайдер доставляет на публичную ловушку, а вы читаете перехват, либо поставьте туннель перед локальным портом. Ловушке не нужны открытые порты: URL не меняется при перезапусках и хранит историю вместе с повторами.
Нужен ли ngrok?
Нет. Туннель нужен только когда ваш обработчик должен работать на вашей машине. Чтобы увидеть, что именно отправляет провайдер — заголовки, тело, повторы — достаточно постоянного URL в облаке.
Какие HTTP-методы поддерживаются?
Перехватываются GET, POST, PUT, PATCH, DELETE, OPTIONS и HEAD вместе с заголовками, query-строкой, телом, IP, user-agent и временем запроса.
Как проверять подпись Stripe или GitHub?
Подписывайте сырое тело, а не разобранный JSON: Stripe подписывает «timestamp.body», GitHub присылает HMAC-SHA256 сырого тела в X-Hub-Signature-256. Сравнивайте функцией с постоянным временем и проверяйте timestamp, чтобы отклонять повторы.
Можно ли дать ссылку коллеге?
Да. Создайте read-only ссылку на вебхук и передайте её: доступ можно отозвать в любой момент.
Перехваченные данные попадут в поисковики?
Нет. Перехваты и ссылки шаринга отдают директивы noindex: краулеры не сохранят payload с реальными токенами и идентификаторами.
Попробуйте сами
Создайте бесплатный аккаунт, сделайте инбокс или URL вебхука и направьте на них своё приложение. Ничего не доставляется реальным получателям.
SMTP-песочница за четыре шага и проверка настоящего письма в CI: примеры для Laravel, Django, Node и командной строки плюс детали, которые ломаются в проде.
Что использовать вместо Mailtrap: Mailpit, smtp4dev и заброшенный MailHog в сравнении с приватно хостируемой песочницей, плюс чек-лист переезда за десять минут.
Хватит вставлять скриншоты в чат: та же read-only ссылка отдаёт JSON или простой текст, чтобы модель проверила тему, заголовки, ссылки и тело каждого письма.
MailHog не поддерживается уже несколько лет. Чем его заменить (Mailpit, smtp4dev, приватно хостируемая песочница), как переехать за пять минут и что вы при этом теряете.
Mailpit вы запускаете сами, MailHog — то, что он заменил, Mailtrap — сервис по подписке: сравнение по обслуживанию, цене, месту хранения писем и командным функциям.
Что такое mailtrap.io, чем ограничен его бесплатный тариф по счётчику, что означают запросы «mail trap», «trap email» и «mailtrap login» — и какие есть бесплатные альтернативы.
Сервис Mailpit для вашего compose-файла: порты, том с данными чтобы письма переживали перезапуск, конфигурации приложений, healthcheck для CI и ошибки, которые стоит знать заранее.
Mail::fake() доказывает, что код вызвал отправку; настоящая SMTP-песочница доказывает, что письмо правильное. Обе настройки, стили проверок, ловушка с очередью и ошибки, из-за которых сломанное письмо уходит в прод.
На что смотреть, когда перехваченные payload-ы настоящие: отзыв доступа, noindex, история запросов, read-only шаринг и машинный экспорт — и как на каждый пункт отвечает этот инстанс.