Команда, разрабатывающая Vestron — органайзер сохранённых постов для Instagram, столкнулась с резким увеличением расходов на Firebase на 1557% за одну ночь. Это произошло без каких-либо изменений в коде и стало настоящим шоком для разработчиков.
Что привело к проблеме
Обнаружилось, что количество вызовов Cloud Function достигло предела, поскольку сервер получал слишком много повторных запросов от Meta. Причина кроется в том, что сервера возвращали некорректный ответ при проверке подписи. Meta интерпретировала это как сбой сервера и увеличила частоту запросов с exponential backoff, что привело к тысячам дублирующих вызовов.
В чем состоит корень проблемы
Разработчики использовали body-parser middleware в Express, который автоматически преобразовывает необработанный JSON в JavaScript-объект. Meta подписывает свои вебхуки с использованием HMAC-SHA256, основываясь на точных необработанных байтах тела сообщения. В результате, как только body-parser вмешивался в данные, они модифицировались, и проверка подписи всегда не проходила.
Что было сделано для решения проблемы
Команда разработала отдельную функцию Firebase (`instagramWebhookV2`), которая полностью обходила Express. Теперь функция:
- Получает необработанный поток байтов через
req.rawBody, - Сразу выполняет проверку HMAC-SHA256 как первую строку кода,
- Возвращает
200 OKк Meta в считанные миллисекунды.
После внедрения нового подхода число повторных вызовов упало до нуля, а счета нормализовались в тот же день.
Технические улучшения и их значение
Старый архитекторский подход: получение вебхука → сохранение в базу этих → вызов функции с холодным стартом → отправка ответа пользователю — занимал 10-15 секунд. Новый же метод сокращает время ответа до менее 2 секунд. Теперь пользователи получают ответы бота в реальном времени, избегая задержек в 15 секунд.
Практические выводы для разработчиков
Этот случай наглядно демонстрирует важность соблюдения правил при обработке вебхуков с использованием проверки подписи по сырому телу. Для всех вебхуков, работающих с подобной верификацией (например, Meta, Stripe, GitHub), не следует позволять middleware изменять тело запроса. Рекомендуется использовать express.raw() с коллбеком verify для сохранения сырого тела без изменений.
В дальнейшем разработчикам стоит обратить внимание на архитектурные решения, которые позволят оптимизировать время обработки запросов. С учетом растущих нагрузок важно, чтобы функции работали как можно быстрее и эффективнее.