Отладка вебхуков без сантехники

Каждая интеграция вебхуков начинается с одной и той же негламурной проблемы: серверу провайдера нужно вызвать ваш код, ваш код работает на вашем ноутбуке, и ничто не может достучаться до вашего ноутбука. Поэтому, прежде чем отлаживать настоящую работу — проверку подписи, форму полезной нагрузки, логику обработчика, — вы сначала строите сантехнику: туннель, развёртывание на промежуточной машине, публичный URL, пересылающий на вашу машину. Эта статья о том, как пропустить этот шаг с помощью временного приёмника, такого как Тестер вебхуков на этом сайте: создайте адрес, направьте на него провайдера, вызовите событие и прочитайте ровно то, что прибыло. Затем пишите обработчик по правде, а не по документации.

Цикл отладки и где он ломается

Здоровый цикл вебхука короток: событие происходит у провайдера, провайдер POST-ит подписанную полезную нагрузку на ваш эндпоинт, ваш эндпоинт проверяет подпись, разбирает тело, делает работу и отвечает 200. Нездоровый цикл — тот, который узнаёт каждый, — добавляет туман вокруг каждого шага. Панель провайдера говорит, что доставка не удалась с 500, или с 404, или не говорит ничего вообще. Отвечал ли это ваш код, прокси между, или URL эндпоинта с опечаткой? Повторная попытка прибывает с тем же id события — или это новое событие для того же платежа? Документация показывает аккуратный пример JSON, но производственные нагрузки несут дополнительные поля, вложенные объекты, которые пример сплющил, и изредка тип содержимого, который пример никогда не упоминал. Документация вебхуков Stripe и GitHub (ссылки ниже) подчёркивает одни и те же дисциплины — проверяйте подписи, отвечайте быстро, обрабатывайте дубли, — и ни одну из них нельзя практиковать, пока вы не сможете увидеть настоящую доставку.

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

Что даёт вам временный приёмник

Временный приёмник — это публичный URL, вся работа которого — принимать всё и показывать вам: Тестер вебхуков на этом сайте создаёт его в один клик, держит 30 минут, записывает до 50 доставок с их методом, путём и запросом, заголовками и телом (каждое до 256 КБ), а затем автоматически удаляет всё. Что это даёт — это последовательность. Вместо того чтобы развёртывать наполовину написанный обработчик на публичной машине только ради наблюдения за трафиком — а затем отлаживать развёртывание и обработчик одновременно — вы отделяете наблюдение от реализации. Сначала смотрите, как прибывают несколько настоящих событий, и изучаете их истинную форму. Затем пишите обработчик локально, по захваченным нагрузкам, которые можете вручную проиграть в свои собственные тесты. Наконец, направьте провайдера на ваш настоящий эндпоинт и вызовите событие ещё раз, уже зная точно, как выглядит правильная доставка и что ваш обработчик должен с ней делать.

Он также даёт доказательства, когда разговор пересекает границу команды. «Твой вебхук отправляет сумму строкой» — это спор; захваченная доставка, показывающая "amount": "2000", — это факт. Тот же захват отвечает на вопросы тикетов поддержки провайдеров — какой id события, какая метка времени, какой заголовок подписи присутствовал — без раунда археологии журналов, и он определяет, была ли отсутствующая доставка никогда не отправлена или отправлена на старый тестовый адрес, который кто-то забыл обновить. Соседний инструмент, та же философия: трассировщик цепочек статусов HTTP показывает, что URL делает с точки зрения сервера, когда вопрос касается перенаправлений, а не нагрузок.

Рабочий процесс с этим инструментом

Первое: откройте Тестер вебхуков и создайте хук. Скопируйте URL приёма — и с этого момента обращайтесь с этим URL как с удостоверением, по причинам, которые излагает следующий раздел. Второе: вставьте его в настройки вебхуков провайдера как тестовый эндпоинт — Stripe, GitHub и большинство провайдеров позволяют несколько эндпоинтов, поэтому ваш производственный эндпоинт остаётся нетронутым — или в конфигурацию вашего собственного сервиса, если вы отлаживаете отправителя, которого контролируете. Третье: вызовите событие честным способом. Используйте настоящий поток провайдера (платёж в тестовом режиме, настоящий пуш в одноразовую ветку), а не только кнопку «отправить тестовое событие» панели, потому что тестовые события иногда отличаются от настоящих именно в тех полях, которые вам важны. Четвёртое: прочитайте захват. Проверьте сначала метод и тип содержимого, затем подпись и заголовки события, затем тело как сырой текст, прежде чем доверять любому приукрашенному его виду.

Пятое: напишите или исправьте обработчик по этому захвату. Проверяйте подпись по сырому телу ровно как получено — именно поэтому захват показывает неразобранные байты — и сделайте обработчик идемпотентным по id события, который вы теперь можете видеть стабильным между повторными попытками. Шестое: удалите хук (или дайте его 30 минутам истечь), уберите тестовый эндпоинт у провайдера и снова направьте доставку на ваш настоящий обработчик. Седьмое: вызовите событие ещё раз и посмотрите, как ваш собственный эндпоинт на него отвечает. Если теперь он падает, разница между захваченной хорошей доставкой и поведением вашего обработчика — это весь баг, и это разница, которую можно проверять, а не угадывать.

Безопасность: URL — это удостоверение

Приёмник, который может читать любой, не нуждается во входе, и у этого удобства есть цена, на которую стоит обратить внимание: id хука в URL — единственное удостоверение, и оно разрешает и отправку, и просмотр. Любой, кто получит URL — из скриншота, журнала чата, общего экрана, — может прочитать каждую нагрузку, которую хук захватывает, пока он живёт. Поэтому правила — это правила паролей: никогда не публикуйте URL, никогда не используйте его для живого производственного потока и удаляйте хук по окончании сеанса, а не позволяйте захватам высиживать их полчаса. Нагрузки заслуживают того же уважения с другой стороны: настоящая платёжная или пуш-нагрузка может содержать адреса электронной почты клиентов, детали заказов и метаданные, которые вы не вставили бы в публичный gist, а захваченная доставка — именно такой класс документа. Тестовый режим, одноразовые репозитории и синтетические события существуют именно для того, чтобы отладочные артефакты не содержали ничего, что стоит красть.

Собственные ограничения тестера — часть истории безопасности, и они ясно изложены на странице инструмента: захваты хранятся на сервере проверки только до истечения хука или его удаления вами — не более 30 минут, — тела ограничены 256 КБ, а хуки — 50 запросами, и приёмник никогда ничего не запрашивает, ни за чем не следует и ничего не повторяет, поэтому захваченная нагрузка не может превратиться в исходящий трафик сервера. Суждения всё равно остаются за вами. Если нагрузка была бы проблемой в руках незнакомца, ей вообще не место в общем просмотрщике: используйте для этого сеанса отладки свою собственную инфраструктуру, как вы поступили бы с производственными секретами. Удобные инструменты зарабатывают своё место на малочувствительной работе, которая составляет большинство дней отладки.

Ограничения: только приём и проверка

Чего этот инструмент намеренно не делает — это всего, что идёт после проверки. Он не пересылает доставки на ваш ноутбук (это работа туннеля, и туннели — правильный инструмент, как только обработчик существует и вы итерируете по нему вживую), он не повторяет захваченный запрос на ваш настоящий эндпоинт, он ничего не преобразует и не подписывает, и он не хранит историю дольше 30 минут. Эти отсутствия и есть дизайн: приёмник, который пересылает, — это прокси, через который течёт ваш трафик, приёмник, который повторяет, — это отправитель, и оба — большее доверие, чем 30-минутный лоток входящих. Используйте тестер для той фазы, к которой он подходит, — видеть правду до и во время постройки обработчика, — и позвольте вашему собственному эндпоинту, за вашей собственной аутентификацией и журналированием, принять эстафету в тот момент, когда интеграция выходит в продукцию.

Источники и дополнительная литература

Часто задаваемые вопросы

Можно ли использовать временный приёмник со Stripe или GitHub?

Да — оба позволяют регистрировать дополнительные эндпоинты вебхуков, поэтому приёмник может сидеть рядом с вашим производственным эндпоинтом, не трогая его. Создайте хук, добавьте его URL в настройки вебхуков провайдера и используйте тестовый режим (Stripe) или одноразовый репозиторий (GitHub), чтобы вызывать настоящие события. Документация обоих провайдеров, ссылки на которую есть в источниках, охватывает регистрацию эндпоинтов и их схемы подписи; проверяйте подписи в вашем обработчике по сырому телу, ровно как показывает захват.

Безопасно ли направлять настоящие производственные вебхуки на общий приёмник?

Нет. Любой с URL хука может прочитать всё, что он захватывает, а настоящие нагрузки рутинно несут данные клиентов. Используйте тестовый режим, песочницы и синтетические события с общим приёмником; держите производственный трафик на своей собственной инфраструктуре. Хук также истекает через 30 минут, поэтому направленный на него производственный поток начал бы тихо падать полчаса спустя — ещё одна причина, почему это инструмент отладки, а не эндпоинт.

Почему тестер не пересылает запросы на мой компьютер?

Потому что приём и проверка — намеренно маленькое доверие: сервер хранит доставку не более 30 минут и никогда не инициирует собственный трафик. Пересылка сделала бы его прокси для произвольного трафика на вашу машину, а повтор сделал бы его отправителем — оба являются другими, большими сервисами с другими рисками. Как только ваш обработчик существует и вы итерируете вживую, туннель — правильный инструмент для этой фазы.