Доставка событий, цифровые подписи, логика повторных попыток и верификация
Webhooks — это механизм, при котором сервер уведомляет клиент о наступлении определенного события, отправляя HTTP-запрос на заранее заданный URL клиента. Это альтернатива "опросу" (polling), где клиент сам периодически запрашивает сервер.
| Компонент | Описание | Стандартные практики | Важные нюансы |
|---|---|---|---|
| Доставка | POST-запрос с JSON payload на URL клиента | Content-Type: application/json, event type в payload | Используйте HTTPS для всех эндпоинтов |
| Безопасность | Цифровая подпись (HMAC) для верификации | Заголовок X-Signature: sha256=abc123 | Секретный ключ должен быть защищен и не передаваться в логах |
| Повторные попытки | Exponential backoff при неудачной доставке | 1 мин, 5 мин, 30 мин, 2 часа, max 5-10 попыток | Не используйте fixed delay — это может перегрузить клиента |
| Идемпотентность | Уникальный ID события для предотвращения дублирования | event_id: "uuid-v4" в payload | Клиент должен хранить обработанные ID для идемпотентной обработки |
| Dead Letter Queue | Хранение неудачных событий после исчерпания попыток | Отправка в очередь сообщений (Kafka, SQS) | Позволяет анализировать причины сбоев и ручную обработку |
timestamp в payload для защиты от повторного использования старых запросовHTTP-запрос от сервера к клиенту:
POST /webhook HTTP/1.1
Content-Type: application/json
X-Signature: sha256=abc123def456...
X-Event-ID: 550e8400-e29b-41d4-a716-446655440000
X-Event-Type: user.created
X-Timestamp: 1709020800
{
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"event_type": "user.created",
"timestamp": 1709020800,
"data": {
"id": "user_123",
"name": "John Doe",
"email": "john@example.com"
}
}Реализация на сервере (Node.js):
const crypto = require('crypto');
// Генерация подписи
const generateSignature = (payload, secret) => {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(payload));
return hmac.digest('hex');
};
// Отправка webhook
const sendWebhook = async (url, event, secret) => {
const payload = {
event_id: uuidv4(),
event_type: event.type,
timestamp: Math.floor(Date.now() / 1000),
data: event.data
};
const signature = generateSignature(payload, secret);
try {
const response = await axios.post(url, payload, {
headers: {
'Content-Type': 'application/json',
'X-Signature': `sha256=${signature}`,
'X-Event-ID': payload.event_id,
'X-Event-Type': payload.event_type,
'X-Timestamp': payload.timestamp
},
timeout: 10000 // 10 секунд тайм-аут
});
if (response.status === 200 || response.status === 204) {
console.log(`Webhook delivered successfully: ${payload.event_id}`);
return true;
}
} catch (error) {
console.error(`Webhook delivery failed: ${payload.event_id}`, error.message);
return false;
}
};
// Middleware для обработки webhook на клиентской стороне
app.post('/webhook', (req, res) => {
const signature = req.headers['x-signature'];
const eventId = req.headers['x-event-id'];
const eventType = req.headers['x-event-type'];
const timestamp = req.headers['x-timestamp'];
// Проверка времени (не старше 5 минут)
if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
return res.status(400).json({ error: 'Timestamp too old' });
}
// Проверка подписи
const expectedSignature = generateSignature(req.body, CLIENT_SECRET);
if (signature !== `sha256=${expectedSignature}`) {
return res.status(401).json({ error: 'Invalid signature' });
}
// Проверка идемпотентности
if (alreadyProcessed(eventId)) {
return res.status(200).json({ status: 'already_processed' });
}
// Обработка события
processEvent(req.body);
// Сохраняем как обработанный
markAsProcessed(eventId);
res.status(200).json({ status: 'success' });
});| Этап | Действия | Ответственные | Сроки |
|---|---|---|---|
| Проектирование | Определение типов событий, формат payload, безопасность | Архитектор, Security Engineer | На этапе проектирования API |
| Разработка | Реализация отправки и приема webhooks, интеграция с очередями | Разработчики | В процессе разработки |
| Тестирование | Тестирование доставки, проверка подписей, нагрузочное тестирование | QA, DevOps | Перед PR и в CI |
| Релиз | Документация для клиентов, примеры, инструменты тестирования | Technical Writer, Release Manager | При релизе |
| Поддержка | Мониторинг доставки, анализ сбоев, обработка dead letter | DevOps, Support Team | Постоянно |
Q: Как правильно проектировать webhooks для клиентов API?
A: POST на URL клиента с логикой повторных попыток (exponential backoff), подписью для верификации (X-Signature header), типом события в payload.
Q: Какой заголовок используется для цифровой подписи webhook-запроса?
A: Наиболее распространенный заголовок — X-Signature, который содержит HMAC-хэш тела запроса, вычисленный с помощью секретного ключа. Клиент использует тот же ключ для проверки подписи.
Q: Какой механизм используется для предотвращения дублирования обработки одного и того же события?
A: В payload webhook-запроса должен быть уникальный event_id. Клиент может использовать этот ID для идемпотентной обработки, игнорируя повторные запросы с тем же ID.
Q: Какой тип логики повторных попыток рекомендуется для webhooks? A: Exponential backoff (например, 1 мин, 5 мин, 30 мин, 2 часа) является стандартной практикой. Он предотвращает перегрузку клиента при его временной недоступности и дает ему время на восстановление.
Правильно спроектированные webhooks обеспечивают надежную и эффективную асинхронную коммуникацию между сервисами.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.