Контур целиком
| Шаг | Что происходит | Что может пойти не так |
|---|---|---|
| 01 · Доставка | Внешний сервис отправляет HTTPS POST | DNS, TLS, сеть или неверный URL |
| 02 · Проверка | Приёмник проверяет подпись и допустимость события | Поддельный или изменённый запрос |
| 03 · Регистрация | Сохраняются ID, тип, время и технический статус | Дубликат или потеря контекста |
| 04 · Подтверждение | Отправителю быстро возвращается 2xx | Таймаут запускает повторную доставку |
| 05 · Обработка | Бизнес-логика выполняется отдельно | Ошибка внешнего API или данных |
| 06 · Наблюдение | Метрики и журнал показывают результат | Сбой остаётся незамеченным |
Проектируйте вебхук с предположением, что одно событие может прийти несколько раз, позже обычного и не в том порядке, в котором происходили действия.
Сначала определите контракт
До настройки URL зафиксируйте:
- источник и владелец интеграции;
- минимальный набор событий;
- идентификатор доставки или события;
- способ проверки подлинности;
- максимальный размер тела запроса;
- допустимое время ответа;
- правила повторов и ручной доставки;
- данные, которые нельзя писать в лог;
- ожидаемое действие в HereCRM;
- способ отключения интеграции.
Подписывайтесь только на нужные типы событий. Это уменьшает нагрузку, объём персональных данных и количество неожиданных веток логики.
Публичный endpoint
Адрес должен быть отдельным HTTPS-маршрутом без секретов в URL.
POST https://hooks.example.com/v1/github
POST https://hooks.example.com/v1/telegram
Не используйте такой вариант:
https://hooks.example.com/webhook?secret=my-secret
Query-параметры попадают в журналы прокси, историю диагностики и системы аналитики. Секрет передаётся или проверяется способом, который определяет конкретный поставщик: HMAC-подпись, специальный заголовок, mTLS или OAuth.
Endpoint должен принимать только ожидаемый HTTP-метод, ограничивать размер запроса и возвращать нейтральную ошибку без внутреннего стека.
Проверяйте сырое тело запроса
Многие поставщики вычисляют HMAC по исходным байтам payload. Если сначала разобрать JSON и собрать его заново, пробелы и порядок сериализации изменятся — подпись перестанет совпадать.
Пример проверки X-Hub-Signature-256 для GitHub:
import hashlib
import hmac
def verify_github_signature(body: bytes, signature: str | None, secret: bytes) -> bool:
if not signature or not signature.startswith("sha256="):
return False
expected = "sha256=" + hmac.new(secret, body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Используйте собственный случайный секрет для каждого webhook, храните его вне кода и сравнивайте значения функцией постоянного времени. Не переносите название заголовка GitHub на другие сервисы — у каждого поставщика свой контракт.
Защита от повторов
Повторная доставка — нормальная часть работы webhook. Причиной может быть таймаут, временная ошибка сети или ручной redelivery.
До запуска бизнес-логики сохраните уникальный ключ:
source + endpoint + delivery_id
Если такой ключ уже успешно обработан, верните успешный ответ и не повторяйте действие. Для событий без ID сформируйте устойчивый ключ из документированных полей, но не используйте случайный UUID при каждом приёме.
Идемпотентность нужна на уровне результата:
- одна оплата не должна дважды увеличить баланс;
- одно сообщение не должно создать два лида;
- одно изменение задачи не должно дважды запустить отчёт;
- повторный статус не должен повторять необратимое действие.
Быстрый ответ и отдельная обработка
Приёмник выполняет только короткую синхронную часть:
- проверяет метод, размер и content type;
- получает сырое тело и обязательные заголовки;
- проверяет подпись;
- проверяет ID на повтор;
- надёжно регистрирует событие;
- возвращает
2xx.
Тяжёлая работа — запросы во внешние API, создание отчёта, обработка файлов — выполняется после подтверждения доставки. Конкретный механизм зависит от архитектуры проекта; публичная статья не фиксирует очередь или брокер, которого может не быть в контуре.
Статусы HTTP
| Код | Когда возвращать | Что ожидает отправитель |
|---|---|---|
200 или 204 | Событие принято либо уже безопасно обработано | Доставка завершена |
400 | Тело не соответствует контракту | Исправление запроса, повтор обычно бесполезен |
401 или 403 | Подпись отсутствует или неверна | Проверка секрета и источника |
404 | Endpoint отключён или неверен | Исправление URL |
409 | Конфликт действительно требует вмешательства | Поведение зависит от поставщика |
429 | Приёмник временно ограничивает поток | Повтор по правилам поставщика |
500–503 | Временный внутренний сбой до надёжной регистрации | Повторная доставка |
После того как событие надёжно зарегистрировано, ошибка фоновой бизнес-логики не должна превращать приём в бесконечные повторы со стороны поставщика. Обрабатывайте её во внутреннем контуре повторов.
Журнал без утечки данных
Для каждой доставки полезно хранить:
| Поле | Пример безопасного значения |
|---|---|
| Источник | github |
| Delivery ID | Идентификатор из заголовка поставщика |
| Тип события | issues.opened |
| Получено | UTC-время приёма |
| Проверка подписи | passed или failed |
| HTTP-ответ | 204 |
| Результат обработки | lead_created |
| Correlation ID | Внутренний идентификатор цепочки |
| Попытки | Количество внутренних повторов |
Не пишите в обычный лог полный payload, токены, подписи, cookies, персональные данные и содержимое сообщений. Если сырой запрос нужен для расследования, храните его ограниченное время в защищённом хранилище с контролем доступа.
Наблюдаемость
Минимальные метрики:
- число доставок по источникам и типам;
- доля отклонённых подписей;
- количество дубликатов;
- время синхронного ответа;
- задержка до завершения обработки;
- ошибки и число повторов;
- возраст самого старого необработанного события.
Алерт нужен не на каждую единичную ошибку, а на потерю функции: рост серии 5xx, накопление необработанных событий, отсутствие ожидаемого трафика или повторяющийся отказ конкретного источника.
Диагностика по слоям
Событие не приходит
Проверьте активность подписки, выбранный тип события, точный URL, DNS и журнал доставок у поставщика. Не меняйте сервер вслепую, пока не видно, была ли попытка отправки.
TLS или соединение
Проверьте сертификат, цепочку доверия, имя домена и доступность с внешней сети. Не отключайте проверку TLS как постоянное решение.
Подпись не совпадает
Убедитесь, что используется сырой body, правильный секрет и актуальный заголовок. Проверьте, не изменяет ли proxy тело или кодировку до верификации.
Отправитель повторяет событие
Сравните время ответа с требованиями поставщика. Проверьте, что 2xx возвращается после надёжной регистрации, а не после всей бизнес-логики.
Ответ успешный, результата нет
Найдите событие по delivery ID и correlation ID, затем проверьте внутренний статус обработки, последнюю ошибку и возможность безопасного повтора.
Особенности источников
GitHub
Используйте случайный webhook secret, проверяйте X-Hub-Signature-256 по сырому телу и сохраняйте X-GitHub-Delivery для защиты от повторов. GitHub показывает журнал deliveries и позволяет выполнить redelivery.
Telegram Bot API
При setWebhook задайте secret_token; Telegram будет передавать его в заголовке X-Telegram-Bot-Api-Secret-Token. Ограничьте allowed_updates только нужными типами. Токен бота не должен появляться в документации, shell history и логах.
Платёжные системы
Не считайте возврат 2xx подтверждением оплаты для пользователя. Сначала проверьте подпись, ID события, сумму, валюту и состояние объекта по контракту провайдера. Все финансовые переходы должны быть идемпотентными и журналируемыми.
Таблицы и пользовательские скрипты
Не отправляйте всю строку «на всякий случай». Сформируйте минимальный payload, удалите персональные данные, вынесите секрет из исходного кода и учитывайте, что пользователь может изменить структуру листа.
Безопасный тест
- Создайте отдельный тестовый endpoint и отдельный секрет.
- Отправьте документированный пример события.
- Проверьте корректную подпись и отказ при изменённом body.
- Повторите один delivery ID и убедитесь, что действие не дублируется.
- Смоделируйте временную ошибку обработки.
- Проверьте внутренний повтор и итоговый статус.
- Убедитесь, что в логах нет секретов и полного payload.
- Только после этого подключайте production-события.
Сервисы просмотра webhook удобны для обезличенного тестового payload. Не отправляйте туда реальные клиентские данные, токены и события production.
Чек-лист выпуска
- HTTPS и проверка сертификата включены.
- Секрет не находится в URL или репозитории.
- Подпись проверяется по сырому телу согласно документации источника.
- Endpoint принимает только нужные события и ограничивает размер запроса.
- Delivery ID защищает бизнес-действие от повторов.
- Событие надёжно регистрируется до ответа
2xx. - Тяжёлая логика вынесена из синхронного ответа.
- Логи не содержат payload и учётные данные.
- Есть метрики, алерт и способ безопасного redelivery.
- Владелец и процедура отключения интеграции известны.
Источники
- GitHub: проверка подписи webhook
- GitHub: рекомендации по эксплуатации webhook
- Telegram Bot API: setWebhook и secret_token
- Stripe: повторы, подписи и дубликаты событий
Точные заголовки, таймауты и политика повторов различаются между поставщиками. Перед подключением сверяйтесь с документацией выбранного сервиса.