Webhooks: ретраи¶
Если первая попытка доставки webhook'а не удалась, мы повторяем по заранее заданному графику.
График ретраев¶
| Попытка | Задержка от предыдущей |
|---|---|
| 1 | — (сразу после события) |
| 2 | +30 секунд |
| 3 | +2 минуты |
| 4 | +10 минут |
| 5 | +1 час |
| 6 | +6 часов |
| (всё) | + ещё +24 часа после 6-й |
После 6 неудач webhook помечается как failed и больше не повторяется
автоматически. Суммарное окно — около 31 часа от момента события.
Что считается провалом¶
Ретрай инициируется при:
- HTTP
5xx(включая 500, 502, 503, 504, 520+ от Cloudflare). - HTTP
429(rate-limit вашего приёмника). - Сетевая ошибка: ECONNREFUSED, ECONNRESET, DNS-фейл, TLS handshake fail.
- Таймаут: ответ не пришёл за 10 секунд.
Не ретраится:
- HTTP
4xxкроме429— считаем, что у вас баг и ретрай не поможет. Дашборд → Deliveries → можно перезапустить вручную. - HTTP
3xx— мы не следуем редиректам.webhook_urlдолжен быть финальным. - HTTP
2xx— успех, ретрай не нужен.
Идемпотентность — обязательно¶
Один и тот же verification.completed для одного verification_id
может прилететь несколько раз:
- Если ваш приёмник ответил
2xx, но мы не успели это зафиксировать до перезапуска (редко, но возможно). - При ручном Retry из дашборда.
Поэтому ваш обработчик обязан быть идемпотентным. Простейший
способ — UNIQUE constraint по (verification_id, event_type):
def handle(payload: dict) -> None:
try:
Delivery.objects.create(
verification_id=payload["verification_id"],
event_type=payload["type"],
payload=payload,
)
except IntegrityError:
# Уже обработали — успех, ничего не делаем.
return
# ... ваша основная логика
Ручной перезапуск¶
Дашборд → Webhooks → Deliveries → найти нужную попытку → кнопка Retry.
При ручном Retry счётчик попыток не сбрасывается — это просто ещё одна попытка сверху, не запускающая новый цикл из 6 попыток.
Что делать, если webhook'и не доходят¶
- Проверьте в дашборде Deliveries — что наша сторона возвращает? Какой HTTP-код пришёл от вашего эндпоинта?
- Если стабильно
5xx— у вас в приёмнике баг. - Если стабильно
4xx— мы передаём что-то, что ваш парсер не принимает. - Если timeout — ваш приёмник отвечает медленнее 10 секунд.
Перенесите тяжёлую обработку в фон, отвечайте сразу
200 OK. - Если connection refused / DNS fail —
webhook_urlстал недоступен. Создавайте новые верификации с актуальным URL.
Фоллбек через поллинг¶
Если вы пропустили webhook (например, ваш приёмник был в офлайне всю 31-часовую цепочку ретраев) — состояние верификации всё равно сохранено в БД. Получите его через:
curl https://app.truenum.ru/api/v1/verifications/<id>/ \
-H 'Authorization: Bearer tn_live_<prefix>_<secret>'
Поле status будет completed / expired / failed. Поле completed_at
и caller_id_received — заполнены если status=completed.
Не злоупотребляйте поллингом — у эндпоинта тот же лимит 120 req/min.
Тонкая настройка graceful degradation¶
Рекомендуемая схема приёма:
- Acknowledge сразу: получили POST → проверили подпись → положили
в очередь / БД → ответили
200 OK. ≤500 мс. - Обработали в фоне: воркер берёт задачу из очереди, делает бизнес-логику. При ошибке — ретрай внутри вашей системы, не полагаясь на наши.
- Не возвращайте
5xxесли можете обработать позже. Если вы ответили5xx6 раз подряд — webhook потерян, придётся восстанавливать через дашборд.