Webhook failure recovery 2026: паттерны для B2B API цифровых товаров
Webhook-доставка в B2B-интеграциях цифровых товаров — это критичный путь: пропущенное событие order.delivered означает, что код карты не уходит клиенту, и поддержка завалена тикетами. В этой статье — production-паттерны, которые мы видим у топовых FoxReload-партнёров с uptime 99.95%+.
1. Exponential backoff с jitter — математика
Простой ретрай каждые 30 секунд убивает ваш receiver при инциденте на нашей стороне (thundering herd). Правильная формула:
function nextRetryDelay(attempt: number): number {
const base = 30_000; // 30s
const cap = 6 * 60 * 60 * 1000; // 6h
const exp = Math.min(base * Math.pow(2, attempt), cap);
const jitter = Math.random() * exp * 0.3; // ±30%
return exp + jitter;
}
FoxReload применяет именно этот алгоритм: попытки 1–8 распределяются в окне 30s → 24h. Caps на 6 часах не дают одному webhook занять воркер навсегда, а jitter ±30% размазывает retry-пики по времени.
2. Dead-letter queue (DLQ)
После 8 неудачных попыток событие должно попадать в DLQ, а не теряться. Production-паттерн — отдельная очередь с manual replay:
// Express + BullMQ
app.post('/webhook/foxreload', async (req, res) => {
const sig = req.header('X-Foxreload-Signature');
if (!verifyHmac(req.rawBody, sig, process.env.WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
const eventId = req.header('X-Foxreload-Event-Id');
const dup = await redis.set(`evt:${eventId}`, '1', 'EX', 86400, 'NX');
if (!dup) return res.sendStatus(200); // already processed
await queue.add('process-event', req.body, {
attempts: 8,
backoff: { type: 'exponential', delay: 30000 },
removeOnFail: false, // keep for DLQ inspection
});
res.sendStatus(200);
});
DLQ-события разбирает on-call инженер: либо replay через POST /v1/webhooks/{id}/replay, либо ручное закрытие заказа в админке.
3. Idempotency keys — обязательно
Webhook-доставка — at-least-once, не exactly-once. Без идемпотентности один и тот же order.delivered может списать остаток дважды. Используйте X-Foxreload-Event-Id как natural idempotency key:
| Storage | Latency | TTL | Cost / 1M events |
|---|---|---|---|
| Redis SETNX | <2ms | 24h | $0.40 |
| Postgres UNIQUE index | 5–8ms | forever | $0.10 |
| DynamoDB ConditionExpression | 8–12ms | 24h | $1.25 |
Для большинства партнёров Redis SETNX оптимален: дёшево, быстро, TTL покрывает retry-окно FoxReload (24h).
4. Alerting на >1% loss
Метрика, которую вы обязаны мониторить: rolling 5-минутный success rate webhook-доставки. Если падает ниже 99% — это incident. Prometheus-правило:
- alert: WebhookDeliveryDegraded
expr: |
(sum(rate(webhook_received_total[5m]))
- sum(rate(webhook_failed_total[5m])))
/ sum(rate(webhook_received_total[5m])) < 0.99
for: 2m
labels: { severity: page }
Алёрт идёт в PagerDuty/Opsgenie, on-call смотрит DLQ и логи. В 80% случаев причина — деплой receiver-сервиса с регрессией: rollback решает за 5 минут.
CTA
Полная webhook-документация FoxReload, replay-эндпоинты и Prometheus-метрики доступны после онбординга — запросите доступ к API.
