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

Backend2026-10-22TryQuickToolBox

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

Вебхуки — это HTTP-колбэки, которые позволяют сервисам уведомлять ваше приложение о произошедших событиях. В отличие от опроса (polling), они обеспечивают обновления в реальном времени. Но чтобы использовать их надёжно, необходимо решить два критических аспекта: подлинность (убедиться, что запрос пришёл от ожидаемого отправителя) и доставка (обработка сбоев и повторных попыток). В этой статье объясняются оба аспекта с практическими примерами кода.

Как работают вебхуки

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

Процесс прост:

  1. Вы регистрируете URL вебхука в сервисе.
  2. Сервис отправляет HTTP POST на этот URL при возникновении события.
  3. Ваша конечная точка обрабатывает данные и возвращает статус 2xx для подтверждения получения.

Без надлежащей проверки любой может отправлять запросы на вашу конечную точку. Без обработки повторных попыток временные сбои могут привести к потере событий. Давайте разберёмся с каждым аспектом.

Защита вебхуков с помощью подписанных данных

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

Как работает подпись HMAC

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

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

const crypto = require('crypto');
const express = require('express');
const app = express();

// Используйте необработанное тело для проверки подписи
app.use(express.raw({ type: 'application/json' }));

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-signature'];
  const secret = process.env.WEBHOOK_SECRET;
  const payload = req.body;

  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send('Invalid signature');
  }

  // Обработка события
  const event = JSON.parse(payload);
  // ... обработка события
  res.status(200).send('OK');
});

Ключевые моменты:

Дополнительные меры безопасности

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

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

На стороне отправителя (если вы отправляете вебхуки)

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

Пример логики повторных попыток на Python:

import time
import requests
from requests.exceptions import RequestException

def send_webhook(url, payload, max_retries=5):
    for attempt in range(max_retries):
        try:
            response = requests.post(url, json=payload, timeout=5)
            if 200 <= response.status_code < 300:
                return True
            elif 400 <= response.status_code < 500 and response.status_code != 429:
                # Ошибка клиента, не повторять
                return False
        except RequestException:
            pass
        # Экспоненциальная задержка с джиттером
        delay = (2 ** attempt) + random.uniform(0, 1)
        time.sleep(delay)
    return False

На стороне получателя

Ваша конечная точка должна:

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

CREATE TABLE processed_events (
  event_id VARCHAR(255) PRIMARY KEY,
  processed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Перед обработкой попытайтесь вставить ID события. Если возникает нарушение первичного ключа, пропустите обработку.

Тестирование вебхуков локально

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

Сравнение: опрос vs вебхуки

Аспект Опрос Вебхуки
Задержка Зависит от интервала Почти в реальном времени
Нагрузка на сервер Высокая (частые запросы) Низкая (только при событиях)
Сложность Простота реализации Требуется проверка подписи и обработка повторных попыток
Надёжность Вы контролируете повторные попытки Зависит от политики повторных попыток отправителя

Часто задаваемые вопросы

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

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

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

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

Следует ли повторять неудачные доставки вебхуков?

Да, но только для временных ошибок (сетевые проблемы, 5xx). Используйте экспоненциальную задержку с джиттером и установите максимальное количество повторных попыток. Избегайте повторных попыток при ошибках 4xx (кроме 429), так как они указывают на проблему с самим запросом.

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