Retry policy
Жизненный цикл одной доставки
emit_event() ──► в Queue
│
▼
dispatcher worker
│
▼
attempt 1 ── delay 1s ──► fail?
│ → attempt 2 ── delay 5s ──► fail?
│ → attempt 3 ── delay 30s ──► fail?
│ → attempt 4 ── delay 5min ──► fail?
│ → attempt 5 (без паузы после) ──► fail?
│
▼
failure_count++ (после 5 подряд → suspend 24ч)
Что считается success
| Response | Action |
|---|---|
| 2xx | success, не retry. failure_count=0 |
| 410 Gone | fail, retry. Логируется как bot_webhook_gone, но webhook НЕ отключается — обрабатывается как обычный провал (ретрай + счётчик до suspend) |
| 4xx | fail, retry. Часто = плохая HMAC подпись или auth issue |
| 5xx | fail, retry. Server-side ошибка |
| Network error / timeout | fail, retry |
Delays между retries
Паузы применяются МЕЖДУ попытками: 1s, 5s, 30s, 5min (после 5-й попытки паузы нет).
Итого ~5.6 минут на один цикл из 5 попыток.
В таблице задержек есть пятое значение 30min, но оно фактически не
применяется: пауза выдерживается только между попытками, поэтому
после финальной 5-й попытки паузы нет.
После полного cycle'а (5 attempts, всё провалилось) — failure_count++.
Auto-suspend
Логика простая, без многоступенчатой лестницы: считается число подряд
неуспешных циклов доставки. Как только failure_count достигает 5,
webhook ставится на паузу на фиксированные 24 часа
(suspended_until = now + 24ч).
| Условие | Действие |
|---|---|
failure_count < 5 | webhook активен, доставки продолжаются |
failure_count >= 5 | suspended_until = now + 24 часа |
Когда suspension истекает, бэк опять пробует доставку. Если она снова
проваливается — failure_count инкрементируется дальше, и при следующем
достижении порога webhook опять уходит в suspend на 24 часа.
Никаких промежуточных ступеней 5 мин / 30 мин / 2 ч / 6 ч нет: либо webhook активен, либо (после 5 подряд провалов) спит ровно 24 часа.
Что сбрасывает failure_count
- Любая успешная доставка (
2xx) →failure_count = 0. Счётчик считает только подряд идущие провалы, поэтому один успех полностью обнуляет его.
Это единственный способ сброса: ручного re-enable или эндпоинта обнуления
failure_count в bots API сейчас нет (роутер вебхуков — только POST/GET/DELETE).
Suspend при этом не ставит enabled=False, а ограничивает доставку через
suspended_until.
Webhook secrets и подпись
Сейчас webhook secret хранится в БД только в виде хэша, а plaintext,
которым подписываются доставки (X-Reasonspace-Signature), держится в
памяти процесса dispatcher'а. Шифрование секрета at-rest —
запланированный TODO (Phase 4.5), ещё не задеплоено.
Практическое следствие: после рестарта или редеплоя API plaintext-секрет
теряется, и доставки временно идут без заголовка X-Reasonspace-Signature
(unsigned), пока секрет не будет задан заново — для этого пересоздайте webhook
(DELETE + POST).
Не полагайтесь на гарантированное наличие подписи: на стороне бота
обрабатывайте отсутствие X-Reasonspace-Signature как отдельный случай
(например, в dev пропускать проверку с предупреждением, а в prod —
решать по своей политике). Подробнее о проверке подписи — в разделе
Подпись webhook'ов.
Idempotency
Один event может прилететь несколько раз (network retry). X-Reasonspace-Delivery
header содержит uniq UUID. Храни последние ~10000 delivery_id в local
cache и дропай dup'ы.
SEEN_DELIVERIES = LruCache(maxsize=10_000)
@app.post("/webhook")
async def webhook(request):
delivery_id = request.headers["X-Reasonspace-Delivery"]
if delivery_id in SEEN_DELIVERIES:
return {"ok": True, "duplicate": True}
SEEN_DELIVERIES[delivery_id] = True
# ... handle ...
SDK делает это автоматически: дедуп идёт по заголовку X-Reasonspace-Delivery
(с фолбэком на поле id из тела события).
Observability
Отдельных метрик вида reasonspace_bot_webhook_* пока нет — не
стройте на них дашборды. Платформа отдаёт только стандартный эндпоинт /metrics
с generic HTTP-метриками.
Состояние доставок отражается в структурированных логах dispatcher'а — по ним можно строить алерты. Реальные события лога:
bot_webhook_queue_full_drop # очередь переполнена, событие отброшено
bot_webhook_gone # endpoint вернул 410 Gone
bot_webhook_auto_suspended # webhook ушёл в suspend (5 подряд провалов)
bot_webhook_url_unsafe # URL не прошёл SSRF-guard
bot_webhook_delivery_network_fail # network error / timeout при доставке
Email-уведомление owner'у при auto-suspend — в roadmap; сейчас факт suspend
фиксируется только в логе (bot_webhook_auto_suspended).