Приёмник вебхуков, который не пропускает чужое и не обрабатывает одно и то же дважды.
Две ошибки встречаются почти в каждой самодельной интеграции.
Первая — подпись либо не проверяется вовсе, либо сравнивается обычным ==, что
даёт утечку по времени, либо считается по перекодированному телу, а не по сырому.
Вторая — нет дедупликации. Провайдер, не получивший 200 вовремя, повторит доставку: так устроены Telegram, WhatsApp Cloud API, платёжные шлюзы. Без защиты клиент получает два ответа на одно сообщение, а бухгалтерия — две записи об одной оплате.
POST /webhook/{provider}
X-Signature: sha256=<hmac>
X-Event-Id: <id доставки> # необязательно, но приоритетнее тела
Порядок проверок: подпись → идентификатор события → дедупликация → ответ. Сначала подпись — разбирать тело неподписанного запроса значит позволять чужому вводу управлять парсером.
Секрет задаётся на каждого провайдера отдельно: GUARD_SECRET_WHATSAPP,
GUARD_SECRET_TELEGRAM. Один общий секрет означает, что утечка у одного
провайдера открывает приём за всех.
GET /deliveries — журнал последних доставок, GET /health — живость и список
настроенных провайдеров.
Отсутствие заголовка подписи — отказ, а не пропуск. Иначе достаточно убрать заголовок, чтобы обойти проверку целиком.
Подпись разбирается и как hex, и как base64, с префиксом и без. Провайдеры присылают её в разном виде и меняют формат без предупреждения.
Повтор получает 200 со статусом duplicate, а не 409. Для провайдера повтор
должен выглядеть как успех, иначе он будет слать его снова по нарастающей.
Дедупликация — на уникальном ключе в базе, а не «сначала SELECT, потом INSERT». Два одновременных повтора при такой схеме проходят оба.
Идентификатор из заголовка важнее, чем из тела. Тело между повторами меняется (порядок ключей, добавленные поля), идентификатор доставки — нет.
Журнал чистится по TTL. Таблица дедупликации, которая растёт вечно, однажды станет причиной инцидента сама.
pip install -r requirements.txt
export GUARD_SECRET_WHATSAPP=...
uvicorn guard.app:app --port 8080
python -m pytest tests -q
18 тестов: подпись в трёх форматах, отказ без заголовка, побитовая чувствительность к телу, независимость провайдеров, атомарность дедупликации, чистка по TTL, извлечение идентификатора события.