Справочник по 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)
}
}Какие вопросы это порождает
Нужно ли добавлять джиттер к значению retry_after?
Прибавлять — да, вычитать — никогда. Джиттер нужен, чтобы множество клиентов не повторяли запрос синхронно, и это реальная забота, когда несколько воркеров делят один токен, — но сдвигать он может только позже названного сервером интервала. Джиттер, способный сократить ожидание, — это баг, переодетый в хорошую практику.
Означает ли 429, что сообщение не отправлено?
Да. Ответ 429 — это отказ, поэтому повтор того же запроса безопасен и ничего не продублирует. Опасен другой случай — сетевой таймаут, при котором сообщение уже могло быть доставлено; именно ему нужен ключ дедупликации, а не этому.
Почему я получаю 429, хотя далеко не дотягиваю до тридцати сообщений в секунду?
Потому что ограничение на отдельный чат живёт отдельно от вашего собственного бюджета. Telegram ограничивает скорость публикации бота в один чат независимо от того, что бот делает в других местах, поэтому всплеск в одну активную группу даёт 429, пока общая скорость выглядит спокойной. Запись идентификатора чата рядом с каждым 429 делает различие мгновенно очевидным.
Может ли retry_after измеряться минутами, а не секундами?
Да, и почти всегда это следствие того, что более ранние и короткие паузы были проигнорированы. Значение растёт с числом нарушений — поэтому первую двухсекундную паузу стоит выдержать точно: дешевле наказание уже не будет.
Смежные ограничения
Как бот получает апдейты и почему от этого зависит место, где живёт шлюз повторов, разбирает сравнение webhook и long polling.
Про проверку того, что входящий апдейт действительно пришёл от Telegram, смотрите страницу о секретном токене вебхука.
Родственную задачу идемпотентности на входящей стороне описывает разбор доставки апдейтов «хотя бы один раз».
Лимит, который чаще всего и отвечает за 429 во время кампании, — это общий потолок отправки на бота.
Очередь, которая реализует этот шлюз, переживает перезапуск и не отправляет дважды, описана на странице сборки рассылочного бота.