Надёжность вебхуков на своём сайте: повторы, идемпотентность, проверка подписи и мониторинг

Вебхук — это не «отправил и забыл», а протокол доставки с подтверждением: если получатель не вернул 2xx за 3 секунды, вы обязаны отправить снова. Для надёжных вебхуков нужно держать четыре вещи одновременно: HM...

Webhook 可靠性四要素:收得到、认得准、不重复、看得见Webhook reliability: receive, verify, dedupe, observe 反直觉:Webhook 不是”发了就行”——对方不回 2xx,你就必须重发Counter-intuitive: a webhook is not sent until a 2xx comes back 要素Element 做法How 量化标准Standard 签名验证Signature HMAC-SHA256 校验头HMAC-SHA256 header check 时间戳容差 5 分钟5-min timestamp skew 快速返回Fast ack 先回 2xx 再异步处理Ack 2xx, process async 3 秒内必须响应Respond within 3s 幂等去重Idempotent 事件 ID 落表去重Dedupe by event id 同一事件重试 N 次只处理 1 次Retry N times, process once 重试退避Retry backoff 指数退避 + 抖动Exponential + jitter 最多 5 至 8 次,约 24 小时5-8 retries over ~24h 核心原则:先验证签名,再快速回 2xx,事件入库后异步处理Verify signature, ack fast, then process async 不要在同步请求里做耗时业务,否则对方判定超时开始重发,造成重复Never do slow work inline or retries will duplicate work 监控红线:重试队列堆积超过 100 条或连续 30 分钟无成功投递,立即告警Alert: backlog > 100 or zero success for 30 min 超过重试上限的事件进死信队列,人工补单,不能静默丢弃Dead-letter failed events; never silently drop
Четыре составляющих надёжного вебхука: проверка подписи, быстрое подтверждение, идемпотентная дедупликация, повторные попытки с задержкой и мониторинг недоставленных

Надёжность вебхуков на своём сайте: повторы, идемпотентность, проверка подписи и мониторинг

Сначала вывод: Вебхук — это не «отправил и забыл», а протокол доставки с подтверждением: если получатель не вернул 2xx за 3 секунды, вы обязаны отправить снова. Для надёжных вебхуков нужно держать четыре вещи одновременно: проверка подписи HMAC для защиты от подделок, сначала быстрый ответ 2xx, потом асинхронная обработка, идемпотентная дедупликация по ID события, повторные попытки с экспоненциальной задержкой и очередью недоставленных. Самая частая авария — не «не получили», а «получили, но обработали дважды», или «обрабатывали слишком долго, сработал повтор, и один заказ превратился в три».

1. Почему вебхуки изначально ненадёжны

Вебхук — это когда чужая система сама отправляет событие POST на ваш callback-адрес. Он работает поверх HTTP, а HTTP не поддерживает постоянное соединение и не гарантирует доставку: моргнула сеть, перезапустился ваш сервис, истёк таймаут обработки — и доставка потеряна. Хуже того, вы не знаете, получил ли получатель: вы отправили, но 2xx не пришёл — то ли сеть потеряла, то ли получатель обработал, но ответ потерялся?

Контринтуитивный вывод здесь такой:Система вебхуков по умолчанию повторяет отправку, поэтому ваш приёмник должен быть рассчитан на то, что доставка будет повторяться, а не на то, что событие придёт один раз. Стандартный подход зрелых платформ вроде Stripe и Shopify: если приёмник не вернул 2xx, отправка повторяется с экспоненциальной задержкой, максимум 5–8 раз в течение примерно 24 часов.

Если не сделать идемпотентность, одно и то же событие оплаты зачислится несколько раз. О том, как связать лиды с системой, читайте Заявки с сайта в CRM, там тоже придётся иметь дело с повторными событиями.

Уберите одно из четырёх — и надёжность упадёт на ступень. В таблице ниже разобрано, что делает каждый элемент и что будет, если его пропустить.

Элемент Что даёт Стандартная практика Что будет, если пропустить
Проверка подписи Убедиться, что запрос действительно от отправителя Проверка заголовка HMAC-SHA256 Поддельные события создадут фальшивые заказы
Быстрое подтверждение Чтобы отправитель не повторял по таймауту Сначала 2xx, потом асинхронная обработка Медленная обработка вызовет повторную доставку
Идемпотентная дедупликация Повторное событие обрабатывается один раз ID события пишется в таблицу для дедупликации Один заказ зачислится несколько раз
Повторы с задержкой Временный сбой в итоге доставляется Экспоненциальная задержка с джиттером Случайный сбой приведёт к потере заказа
Мониторинг недоставленных Неудачные события не теряются молча После лимита — очередь недоставленных и алерт Заказы теряются, и никто не знает

2. Проверка подписи: убедиться, что запрос не подделан

URL вебхука доступен из интернета, и любой может отправить на него POST-запрос. Если не проверять подпись, злоумышленник просто сформирует фальшивое событие «платёж прошёл» — и ваш бэкенд вполне может выдать ему доступ или оформить заказ. Это не теоретический риск, а реальная атака, которой уже пользуются.

Стандартный подход — HMAC-SHA256: отправитель подписывает тело запроса общим секретным ключом и кладёт подпись в заголовок (например, X-Signature). Вы, получив запрос, считаете подпись тем же ключом по исходному телу и сравниваете — обрабатываете только при совпадении.

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

Что проверяем Как сделать Что делать, если не пройдено
Совпадает ли подпись Пересчёт и сравнение HMAC-SHA256 Сразу отклонить с 401
Свежая ли метка времени Расхождение с серверным временем ≤ 5 минут Отклонить, защита от повтора
Не истёк ли ключ Ротация со сосуществованием старого и нового ключей Повторить со старым ключом
Не изменено ли тело запроса Считать подпись по исходным байтам Не декодировать JSON перед подписью

Три. Быстрое подтверждение и асинхронная обработка: не делайте тяжёлую работу в запросе

Это самая частая ошибка. Получив вебхук, многие в том же синхронном запросе делают кучу тяжёлых дел: лезут в базу, шлют письма, дёргают сторонние API, пишут логи — и всё это занимает 5 секунд. Отправитель ждёт 3 секунды, не получает 2xx, решает, что таймаут, и начинает повторять — а у вас ещё обрабатывается первый запрос, и вот приходит второй.

Правильная схема — из трёх шагов: получив запрос, сначала проверьте подпись (это миллисекунды), сразу сохраните событие как есть и верните 2xx, а тяжёлую обработку вынесите в фоновую задачу. Тогда отправитель гарантированно получит подтверждение за 3 секунды и не станет повторять; тяжёлая работа может идти медленнее — пользователь уже получил ответ. Неудачные события остаются в очереди, и асинхронная задача сама их повторяет.

Пример из практики: Приходит событие об успешном платеже. Приёмник делает только три вещи: проверяет подпись, записывает ID события и исходный payload в таблицу events и сразу возвращает 200. А настоящие «списать товар, отправить письмо, уведомить ERP» выполняет фоновый worker, который забирает из таблицы events необработанные события и не спеша их разбирает. Даже если письмо отправляется 10 секунд, на повторные запросы отправителя это не влияет. Как обеспечить надёжность доставки форм, можно посмотреть в Надёжность формы запроса.

Этап Действие Цель по времени Действия при сбое
Синхронный приём Проверка подписи + запись в базу + ответ 2xx Меньше 300 миллисекунд При неверной подписи сразу отклонить
Асинхронная обработка Запросы к базе, уведомления, вызовы внешних сервисов От секунд до минут При ошибке остаётся в очереди на повтор
Повтор Перезапуск с интервалом отката Экспоненциальный откат После превышения лимита — в очередь недоставленных
Ручное доведение Разобрать очередь недоставленных и запустить вручную Ежедневная проверка Записать причину доведения

Четыре. Идемпотентность и повторы: одно и то же событие приходит десять раз, а обрабатывается один

Идемпотентность означает: выполнить операцию один раз или десять раз — результат одинаков. Webhook обязан быть идемпотентным по своей природе, потому что вы не можете запретить отправителю повторять запрос. Реализуется это просто: каждое событие приходит с глобально уникальным ID, вы перед обработкой проверяете таблицу events, и если этот ID уже встречался — сразу возвращаете успех и пропускаете обработку.

Стратегия повторов — экспоненциальная задержка с джиттером: первая неудача — ждём 1 минуту, вторая — 2 минуты, третья — 4 минуты, и так с удвоением, максимум 5–8 попыток, общий интервал около 24 часов. Джиттер нужен, чтобы все неудачные события не ринулись на повтор в одну и ту же секунду и не обрушили ваш сервис ещё раз (это называется штормом повторов). События, исчерпавшие лимит попыток, не теряются, а попадают в очередь недоставленных — до вмешательства человека.

Номер попытки Интервал задержки (примерно) Суммарное время Пояснение
1-я попытка 1 минута 1 минута Временный сбой сети
2-я попытка 2 минуты 3 минуты Кратковременная перезагрузка сервиса
3-я попытка 4 минуты 7 минут Временная недоступность получателя
4-я попытка 8 минут 15 минут Более длительный сбой
5–8-я попытки 15–30 минут Около 24 часов Снова неудача — в очередь недоставленных

Пять: мониторинг и недоставленные — не дайте заказам пропадать молча

Самый коварный сбой Webhook — «тихая потеря заказа»: отправитель считает, что всё доставлено, у вас нет ни одной записи, а через несколько недель клиент жалуется: «Я заплатил, почему нет доставки?» — и только тогда вы обнаруживаете, что событие вообще не пришло. Поэтому мониторинг должен следить за тремя вещами: доля успешных доставок, накопление очереди повторов, размер очереди недоставленных.

Пороги — конкретные числа: если в очереди повторов больше 100 записей или в течение 30 минут нет ни одной успешной доставки — немедленно шлите алерт в привычный вам канал. Очередь недоставленных проверяйте раз в день, при накоплении — восстанавливайте заказы вручную. Если доля успешных доставок падает ниже 99% — разбирайтесь: в здоровом состоянии успешность Webhook должна быть близка к 99,9%, а падение ниже 99% говорит о постоянной проблеме. Как настроить общий мониторинг и алерты, читайте в Мониторинг сайта и оповещения.

Метрика Здоровое значение Порог алерта Действие
Доля успешных доставок ≥ 99,9 % 1 час подряд < 99% Проверьте логи приёмника
Накопление очереди повторов близко к 0 Больше 100 записей Проверьте, не завис ли получатель
Очередь недоставленных 0 Любое накопление Восстановите вручную и найдите причину
Нет успешных доставок подряд События идут постоянно 30 минут — ноль Проверьте, не заблокирован ли адрес колбэка
Число неудачных проверок подписи близко к 0 Рост в 10 раз Возможно, кто-то сканирует интерфейс

FAQ: частые вопросы

Почему мой Webhook иногда получает повторные события о заказе?
Потому что отправитель автоматически повторяет запрос, если вы вовремя не вернули 2xx. Зрелые платформы по умолчанию повторяют до 5–8 раз в течение примерно 24 часов. Решение не в том, чтобы заставить отправителя не повторять, а в том, чтобы у себя дедуплицировать события по ID: получив событие, сначала проверьте таблицу, и если оно уже обработано — сразу верните успех и пропустите бизнес-логику. Повторная доставка для Webhook — норма, и проектировать нужно исходя из того, что повторы будут.
Webhook нужно обрабатывать сразу или сначала вернуть ответ?
Сначала отвечаем. Как только подпись проверена, сразу сохраняем событие в базу и в течение 3 секунд возвращаем 2xx, а реальную обработку выносим в фон. Если в синхронном запросе вы шлёте письмо, лезете в базу и дёргаете внешние сервисы по 5 секунд, отправитель решит, что истёк таймаут, и начнёт повторять — получите дубли. Асинхронный воркер обработает не спеша, это не страшно.
А можно не проверять подпись, всё равно же HTTPS?
Нет. HTTPS защищает только от прослушивания в пути, но ваш callback URL доступен из интернета, и кто угодно может отправить на него POST. Без проверки подписи злоумышленник подделает событие об успешной оплате и заставит вас отгрузить товар или выдать доступ. Нужно проверять подпись в заголовке через HMAC-SHA256 и добавить допуск по метке времени 5 минут для защиты от повторного воспроизведения.
Как задать интервалы между повторами и почему нельзя повторять каждую минуту?
Используйте экспоненциальную задержку со случайным разбросом: 1 минута, 2, 4, 8 — всего 5–8 попыток примерно за 24 часа. Проблема фиксированного интервала в минуту в том, что при сбое внешнего сервиса все неудачные события повторятся в одну и ту же секунду и снова положат только что поднявшийся сервис — это называется штормом повторов. Случайный разброс разносит их по времени.
Что делать с событиями, которые не прошли и после множества повторов?
Нельзя молча их выбрасывать — они должны попадать в очередь недоставленных. События, превысившие лимит повторов, храните в отдельной таблице, раз в день просматривайте, после ручной проверки запускайте досылку и фиксируйте причину сбоя. Молча выбрасывать — самое опасное: через несколько недель клиент пожалуется, что товар не пришёл, а вы в логах даже не найдёте, что это событие вообще приходило.
Как понять, что Webhook тихо теряет события?
Следите за тремя показателями: если доля успешных доставок ниже 99% — разбирайтесь, если в очереди повторов больше 100 записей — алерт, если 30 минут подряд нет ни одной успешной доставки — немедленный алерт. В здоровом состоянии доля успешных доставок должна быть близка к 99,9%. И ещё одно: каждый день проверяйте, что очередь недоставленных пуста. С такими алертами тихую потерю событий вы найдёте в тот же день, а не через недели по жалобе клиента.

В заключение

Главное, что нужно понимать про надёжность Webhook: это не односторонний push, а двусторонний протокол по принципу «нет подтверждения — повторяем». Проектируя приёмник, исходите из того, что доставка будет повторяться, события будут подделывать и они будут теряться, — это норма, а не редкий случай. Из четырёх элементов идемпотентность с дедупликацией и быстрое подтверждение чаще всего упускают, и именно они дают больше всего аварий. Сделайте сначала их правильно — и число инцидентов упадёт вдвое.

Три шага к надёжности: первый — на приёмной стороне только проверка подписи, запись в базу и ответ 2xx за 3 секунды, вся бизнес-логика асинхронно; второй — таблица дедупликации по ID события, повторные попытки с экспоненциальной задержкой и джиттером; третий — ежедневный обход очереди недоставленных, алерты на долю успешных доставок и объём накоплений. После этих трёх шагов ваш колбэк-канал превращается из «заказы теряются, как повезёт» в «потеряли — в тот же день заметили и восстановили».

Запомните одну фразу: надёжность Webhook зависит не от того, как стабильно отправляет другая сторона, а от того, как тупо принимаете вы — проверяй подпись, отвечай быстро, убирай дубли. Лучше обработать дважды, чем потерять заказ.

crater
crater Эксперт Crater

Официальный аккаунт организации · crater Crater

环形山 (Crater) — компания, которая помогает выходить на зарубежные рынки: разработка сайтов на WordPress, Google SEO и комплексный рост в GEO. Мы работаем с умными устройствами, промышленным оборудованием, SaaS и новой энергетикой.

Возможности продуктаБелая книга по методологииОтраслевые отчёты
965篇分享
78статей в базе знаний