Мой первый опыт обработки вебхуков: как я учился делать надёжный бэкенд на Python
Я студент, изучаю backend-разработку на Python. Недавно в рамках учебного проекта столкнулся с задачей: нужно было сделать интеграцию с платёжным сервисом. Они присылают уведомление (вебхук), когда пользователь оплатил заказ, а я должен обновить статус в базе.
Поначалу я думал: «Что тут сложного? Просто эндпоинт напишу». Но когда начал копаться глубже, выяснилось, что всё не так просто. В этой статье хочу рассказать, как я пришёл от простого скрипта к архитектуре с очередью задач, и какие грабли при этом собрал. Надеюсь, мой опыт поможет другим новичкам не наступать на те же шишки.
Как я сделал сначала (и почему это было плохо)
Первая версия моего кода выглядела примерно так:
Логично же? Пришло событие → обновил базу → отправил письмо. На локальном сервере всё работало идеально. Но когда я попробовал протестировать это под нагрузкой (и просто на реальном интернете), начались странности.
- Таймауты. Платёжный сервис ждал ответа не больше 5 секунд. Если база данных тормозила или сервис отправки писем долго отвечал, я получал ошибку.
- Потеря данных. Один раз мой сервер упал прямо во время обработки. В итоге деньги у клиента списались, а у меня заказ остался в статусе «ожидает».
- Дубли. Иногда приходило два одинаковых уведомления. Моя база данных пыталась создать два одинаковых заказа, и вылезала ошибка уникальности.
Я понял, что так работать нельзя. Нужно разделять «приём» и «обработку».
Как я искал решение
Начал гуглить «как надёжно обрабатывать фоновые задачи». Наткнулся на понятие очередей задач (Task Queues).
Идея мне понравилась:
- Вебхук просто кладёт задачу в очередь и сразу отвечает «ОК».
- Отдельный процесс (воркер) в спокойном темпе забирает задачи из очереди и делает всю тяжёлую работу.
Для очереди я выбрал Redis. Почему? Потому что он простой, быстрый и его легко поднять через Docker. Для веб-сервера взял FastAPI — он современный и асинхронный.
Архитектура, которая у меня получилась
Я нарисовал схему, чтобы самому лучше понять, как данные бегают. Вот что получилось:
Теперь даже если воркер упадёт, задача останется в Redis и дождётся перезапуска. А API отвечает мгновенно, поэтому платёжный сервис не ругается на таймауты.
Чтобы было понятнее, как это выглядит в системе, вот общая схема компонентов:
Мне нравится, что я могу запустить хоть 10 воркеров, если задач станет много. Это называется горизонтальное масштабирование.
Реализация: код, который у меня работает
Делюсь кодом. Он не идеален, но работает в моём проекте.
1. Приёмщик (API)
Самое важное здесь — не тормозить. Я только проверяю подпись и кидаю в Redis.
2. Воркер (Обработчик)
Это отдельный скрипт, который я запускаю через терминал (или через systemd/Docker в продакшене). Он крутится в бесконечном цикле.
С какими проблемами я столкнулся
Не всё прошло гладко, хочу честно рассказать о багах.
- Забыл про подписи. Сначала я не проверял подпись вебхука. Потом прочитал документацию платёжки и понял, что любой человек может отправить POST-запрос на мой сервер и создать себе баланс. Пришлось срочно добавлять проверку HMAC.
- Redis пропадал. Когда я перезагружал компьютер, данные в Redis исчезали (он же в памяти). Для учебного проекта это ок, но для реального нужно включать persistence (сохранение на диск) или использовать базу данных как очередь.
- Бесконечный цикл ошибок. Если в коде воркера ошибка, задача возвращалась в очередь и сразу же забиралась снова. Воркер уходил в цикл и грел процессор. Сейчас я добавил задержку (time.sleep) и счётчик попыток.
Что я понял в итоге
Эта задача помогла мне разобраться не только в вебхуках, но и в архитектуре в целом.
Что это дало:
- API стал отвечать за 50 мс вместо 2 секунд.
- Я перестал бояться, что сервер упадёт и данные потеряются.
- Я научился работать с Redis не только как с кэшем, но и как с очередью.
Заключение
Для меня это был большой шаг от «просто кода» к «инженерному решению». Если вы тоже только начинаете и делаете интеграции — не ленитесь внедрять очереди сразу. Это сэкономит вам кучу нервов потом.