Telegraft

Справочник по Bot API

Что делает Telegram, когда вы отправляете слишком быстро

Превышение лимита Telegram возвращает HTTP 429 и тело JSON с полем `parameters.retry_after` — целым числом секунд. Это указание, а не оценка: подождать меньше значит усилить наказание, а выдержать названный интервал и повторить тот же запрос — это и есть вся правильная обработка целиком.

retry_after — предписанная пауза: точные цифры

Ответ при превышении лимита
HTTP 429 и поле parameters.retry_after в целых секундах
Код ответа
HTTP 429
Какое поле читать
parameters.retry_after, целое число секунд
Правильная пауза
Ровно названный интервал — никогда не угаданный покороче
На что распространяется пауза
На всю очередь отправки, а не на отдельный запрос

As of 2025-10-01, Telegram Bot API 13.4

Что это значит на практике

В ответе есть всё, что нужно для восстановления. Поле `ok` равно false, `error_code` содержит 429, `description` читается как «Too Many Requests: retry after N», а `parameters.retry_after` хранит это N целым числом. Любая приличная клиентская библиотека отдаёт поле наружу; если используемая не отдаёт, стоит читать сырую ошибку, а не откатываться на универсальный экспоненциальный бэкофф, потому что именно угаданный интервал превращает двухсекундную паузу в двухминутную.

Самая разрушительная ошибка здесь сделана из лучших побуждений. Обычный клиент HTTP с экспоненциальным бэкоффом и джиттером повторит запрос через долю секунды, то есть намного раньше интервала, который сервер только что назвал. Сервер видит клиента, игнорирующего указание, и продлевает ожидание; клиент немного увеличивает паузу и повторяет снова; бот проводит минуты без доступа к API, которым мог бы пользоваться уже через две секунды. Стандартная политика повторов против этого API прямо вредна, и её надо заменять, а не подкручивать.

Ждать должна очередь, а не отдельный запрос. Если поставить на паузу только упавший вызов, все прочие воркеры и все прочие сообщения в очереди продолжат давить на тот же бюджет и немедленно воспроизведут то же состояние. Общая отметка «не раньше», которую проверяет каждый отправитель, превращает один 429 в одну паузу; обработка на уровне запроса превращает его в шторм.

Идемпотентность важна ровно на границе повтора, и испортить её легко. Ответ 429 означает, что запрос отклонён, поэтому повтор безопасен, — но таймаут это другой отказ, при котором сообщение вполне могло уйти до обрыва соединения. Повторять таймаут без ключа дедупликации — это ровно то, как подписчики получают рассылку дважды, и чинится это в очереди: отметить отправку как начатую до вызова и свести данные после.

Часть ответов 429 информативна, а не карательна. Telegram притормаживает чат, который бот заливает сообщениями, даже когда сам бот далеко не выбрал свой бюджет, и retry_after здесь относится к чату-получателю, а не к боту. Обработка одинаковая, а диагноз разный: запись идентификатора чата рядом с каждым 429 — это то, что отличает «мы в целом шлём слишком быстро» от «вот эта одна группа перегружена».

Как это обрабатывать в коде

// One shared gate, honoured by every sender. Not a per-request backoff.
let notBefore = 0

function retryAfterOf(error: unknown): number | undefined {
  const p = (error as { parameters?: { retry_after?: number } }).parameters
  return p?.retry_after
}

async function callWithFloodControl<T>(fn: () => Promise<T>): Promise<T> {
  const wait = notBefore - Date.now()
  if (wait > 0) await new Promise((r) => setTimeout(r, wait))

  try {
    return await fn()
  } catch (error) {
    const retryAfter = retryAfterOf(error)
    if (retryAfter === undefined) throw error

    // The server named the interval. Use it verbatim, and hold every other sender too.
    notBefore = Date.now() + retryAfter * 1000
    return callWithFloodControl(fn)
  }
}
Шлюз живёт в общем состоянии, а не в локальной переменной, — и в этом вся разница между «один 429 дал одну паузу» и «один 429 устроил шторм во всех воркерах сразу».

Какие вопросы это порождает

Нужно ли добавлять джиттер к значению retry_after?

Прибавлять — да, вычитать — никогда. Джиттер нужен, чтобы множество клиентов не повторяли запрос синхронно, и это реальная забота, когда несколько воркеров делят один токен, — но сдвигать он может только позже названного сервером интервала. Джиттер, способный сократить ожидание, — это баг, переодетый в хорошую практику.

Означает ли 429, что сообщение не отправлено?

Да. Ответ 429 — это отказ, поэтому повтор того же запроса безопасен и ничего не продублирует. Опасен другой случай — сетевой таймаут, при котором сообщение уже могло быть доставлено; именно ему нужен ключ дедупликации, а не этому.

Почему я получаю 429, хотя далеко не дотягиваю до тридцати сообщений в секунду?

Потому что ограничение на отдельный чат живёт отдельно от вашего собственного бюджета. Telegram ограничивает скорость публикации бота в один чат независимо от того, что бот делает в других местах, поэтому всплеск в одну активную группу даёт 429, пока общая скорость выглядит спокойной. Запись идентификатора чата рядом с каждым 429 делает различие мгновенно очевидным.

Может ли retry_after измеряться минутами, а не секундами?

Да, и почти всегда это следствие того, что более ранние и короткие паузы были проигнорированы. Значение растёт с числом нарушений — поэтому первую двухсекундную паузу стоит выдержать точно: дешевле наказание уже не будет.

Смежные ограничения