Перейти к основному содержимому

Error handling

HTTP errors от bot-API

401 Unauthorized

Битый / отсутствующий / неизвестный токен. Логируйте и алертите владельца (не retry). SDK перехватывает и raise'ит BotAPIError(401).

Suspended / revoked / deleted бот возвращает 403 Forbidden (detail="bot is suspended" и т.п.), а не 401. 401 — только про сам токен.

403 Forbidden

Возможные причины:

  • Missing scope ("missing scope: messages.send")
  • Channel не в allowed_channel_ids
  • Bot suspended владельцем

Не retry — требуется вмешательство владельца.

404 Not Found

Указан несуществующий resource (например, удалённое сообщение). Логируйте и proceed.

422 Unprocessable Entity

Validation. Баг в коде бота — fix и redeploy.

429 Too Many Requests

Rate limit. Уважение Retry-After. SDK делает auto-respect.

5xx Server Error

Серверная проблема. SDK не ретраит 5xx — сразу поднимает BotAPIError; повтор/обработка на стороне вашего кода. Ретраится только 429 (с уважением Retry-After, иначе фикс. пауза 1.0s, до 3 попыток). При устойчивом повторении — алерт через status.reasonspace.ru.

Webhook errors

Всегда возвращайте 2xx, даже если внутренний handler упал. Иначе получится retry-storm и затем suspension.

@bot.on("message.created")
async def handler(event):
try:
await do_stuff(event)
except Exception as e:
logger.exception("handler failed", exc_info=e)
# НЕ raise — SDK вернёт 200 ОК наружу

SDK ловит exceptions внутри handler'а автоматически (try/except в цикле обработчиков Bot._dispatch) и всё равно отвечает 200. Дополнительная обработка не нужна.

Когда возвращать 4xx/5xx из webhook

Только если требуется retry:

  • 5xx — бэк ретраит с exponential backoff (1s, 5s, 30s, 5min, 30min)

Возврат 410 Gone из вашего webhook не отключает доставку навсегда. Он засчитывается как обычная неудачная доставка (лог bot_webhook_gone, failure_count++); 5 неудач подряд → авто-suspend webhook'а на 24 часа. Постоянного отключения по 410 нет.

Idempotency

Один event может прилететь несколько раз. SDK имеет встроенный delivery-cache по X-Reasonspace-Delivery (LRU 1024 по умолчанию, настраивается через Bot(max_dedup=...)), но при наличии side-effects (запись в ваше хранилище, external API) — добавьте собственный uniqueness check:

@bot.on("message.created")
async def handler(event):
if await store.exists(f"processed:{event.id}"):
return
try:
await process(event)
await store.set(f"processed:{event.id}", True, ex=86400)
except Exception:
# Не set'им processed — будет retry
raise

Network failures (исходящие вызовы)

Сетевые ошибки SDK не оборачивает в BotAPIErrorhttpx пробрасывает httpx.RequestError / httpx.TimeoutException как есть. Конвенции status_code == 0 в SDK нет; ловите сетевые ошибки отдельно от BotAPIError:

import httpx

try:
await bot.send_message(channel_id, "hi", space_id=space_id)
except httpx.TimeoutException:
# Network/timeout — retry с backoff
await asyncio.sleep(1)
await bot.send_message(channel_id, "hi", space_id=space_id)
except httpx.RequestError as e:
# Прочие сетевые ошибки (DNS, connect) — retry/алерт по контексту
logger.warning("network error: %s", e)
except BotAPIError as e:
if e.status_code == 401:
# Token issue — алерт, не retry
raise
else:
# Лог и решение по контексту
logger.warning("send failed: %s", e)

Graceful shutdown

import signal

def _signal_handler(signum, frame):
logger.info("shutting down")
# Очистка voice sessions, файлов и т. п.
asyncio.create_task(cleanup())

signal.signal(signal.SIGTERM, _signal_handler)
signal.signal(signal.SIGINT, _signal_handler)

Особенно важно для voice-ботов: при join бот получает токен и координаты голосовой комнаты — POST /api/bot/spaces/{space_id}/voice/{channel_id}/join отдаёт ровно token / url / room / ttl_seconds. По соглашению бот только публикует звук (publish-only). Выход из комнаты делается отключением голосового клиента на стороне бота; REST-метода «notify left» в платформе нет.

Дальше

Observability

Логирование, метрики, alerting.