Вебхуки: подписанные данные и повторные попытки

Backend2026-10-14TryQuickToolBox

Почему вебхуки — это сложно

Вебхуки — простая идея: вместо опроса API на предмет изменений провайдер отправляет события на ваш эндпоинт через HTTP POST. Но на практике вебхуки создают ряд проблем, с которыми сталкиваются многие разработчики: проверка того, что запрос действительно пришёл от провайдера, обработка дублирующихся доставок и сбоев сети. Если игнорировать эти аспекты, вы рискуете безопасностью, целостностью данных и лояльностью пользователей.

Эта статья рассматривает два наиболее критичных аспекта production-вебхуков: подписанные данные и повторные попытки. Мы расскажем, как проверять подписи, как реализовать повторные попытки с экспоненциальной задержкой и как сделать обработку идемпотентной.

Подписанные данные: подтверждение подлинности

Любой может отправить HTTP POST на ваш эндпоинт. Чтобы убедиться, что запрос пришёл от вашего провайдера, провайдер подписывает данные секретным ключом и отправляет подпись в заголовке. Ваша задача — пересчитать подпись и сравнить её.

Большинство провайдеров используют HMAC-SHA256. Подпись обычно вычисляется по необработанному телу запроса (иногда с временной меткой) и кодируется в hex. Например, Stripe использует заголовок вида Stripe-Signature, содержащий временную метку и подпись.

Как работает HMAC

HMAC (код аутентификации сообщений на основе хэша) объединяет секретный ключ с хэш-функцией. При одинаковых ключе и сообщении результат детерминирован. Без ключа злоумышленник не может подделать действительную подпись.

Вот минимальный пример на Node.js:

const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

На Python это столь же просто:

import hmac
import hashlib

def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Лучшие практики проверки подписи

Обработка временных меток и атак повторного воспроизведения

Многие провайдеры включают временную метку в заголовок подписи. Например, заголовок может выглядеть так: t=1614556800,v1=abc123.... Вам следует:

  1. Извлечь временную метку и подпись.
  2. Проверить, что временная метка находится в допустимом окне (например, 5 минут).
  3. Пересчитать подпись, используя временную метку и тело запроса.
  4. Безопасно сравнить подписи.

Это предотвращает повторное воспроизведение старого действительного запроса злоумышленником.

Повторные попытки: изящная обработка сбоев

Вебхуки доставляются через публичный интернет, поэтому сбои неизбежны. Ваш эндпоинт может быть недоступен, работать медленно или возвращать ошибку 500. Надёжный провайдер будет повторять неудачные доставки, но если вы сами являетесь провайдером, вам также нужно обрабатывать повторные попытки на своей стороне.

Экспоненциальная задержка с джиттером

При повторных попытках избегайте немедленных повторов, которые перегружают эндпоинт. Используйте экспоненциальную задержку: увеличивайте время ожидания после каждой неудачи. Добавьте джиттер (случайность), чтобы избежать проблемы «стада» (thundering herd), когда множество вебхуков терпят неудачу одновременно.

Типичное расписание повторных попыток может выглядеть так:

ПопыткаЗадержка (прибл.)
1Немедленно
230 секунд
35 минут
430 минут
52 часа

После максимального числа попыток пометьте доставку как неудачную и отправьте уведомление.

Идемпотентность: ключ к безопасным повторным попыткам

Поскольку повторные попытки могут вызывать дублирующиеся доставки, ваш обработчик вебхуков должен быть идемпотентным. Это означает, что обработка одного и того же события несколько раз должна иметь тот же эффект, что и однократная обработка.

Как достичь идемпотентности:

Реализация повторных попыток на стороне провайдера

Если вы создаёте систему вебхуков, вам нужен надёжный механизм доставки. Вот упрощённый подход:

  1. При возникновении события поставьте в очередь задание на доставку с телом запроса и целевым URL.
  2. Воркер берёт задание и отправляет HTTP POST с таймаутом (например, 10 секунд).
  3. Если статус ответа не 2xx, запланируйте повторную попытку с экспоненциальной задержкой.
  4. Отслеживайте попытки и прекращайте после максимума (например, 5 попыток).
  5. Логируйте сбои и предоставьте пользователям возможность ручного повтора.

Используйте постоянную очередь (например, Redis, RabbitMQ или таблицу в базе данных), чтобы повторные попытки переживали перезапуски.

Собираем всё вместе: чек-лист получателя вебхуков

При создании получателя вебхуков следуйте этому чек-листу:

FAQ

Что такое подписанные данные вебхука?

Подписанные данные включают криптографическую подпись (обычно HMAC-SHA256), сгенерированную с общим секретом. Получатель пересчитывает подпись, чтобы убедиться, что запрос пришёл от ожидаемого отправителя и не был изменён.

Зачем вебхукам повторные попытки?

Проблемы с сетью, простои сервера или временные ошибки могут привести к сбою доставки вебхука. Повторные попытки с экспоненциальной задержкой гарантируют, что событие в конечном итоге будет доставлено, не перегружая получателя.

Как предотвратить дублирующую обработку вебхуков?

Используйте идемпотентность: включайте уникальный ID события в тело запроса, сохраняйте обработанные ID и проектируйте операции идемпотентными (например, upsert). Таким образом, даже если вебхук доставлен несколько раз, эффект будет одинаковым.

Если вам нужно просмотреть или отладить данные вебхуков, JSON Formatter поможет быстро отформатировать и проверить JSON-данные.