← Все статьи
Руководство

Telegram Bot API 429: обработка retry_after в очереди отправки

Если отправка через Telegram Bot API отклонена механизмом ограничения частоты и ответ содержит parameters.retry_after, подождите не меньше указанного числа секунд перед повтором. Сохраните срок ожидания и согласуйте его между всеми воркерами одного бота: пауза в одном процессе не останавливает остальные. Сетевой тайм-аут — другой случай. Без ответа результат отправки может быть неизвестен, поэтому такой запрос нельзя автоматически направлять в ту же ветку повторов.

Главное

  • Читайте структурированный ответ, а не извлекайте число из description.
  • Документированные ограничения — условия планирования, а не гарантия пропускной способности.
  • Отделяйте надёжное сохранение входящих событий от ожидания исходящей отправки.
  • Сохраняйте срок ожидания при перезапуске и проверяйте актуальность отложенного ответа.

Что означают ограничения Telegram

Официальный Bots FAQ рекомендует избегать отправки более одного сообщения в секунду в один чат. Для группы указан предел 20 сообщений в минуту, а для массовых уведомлений — примерно 30 сообщений в секунду без включённых платных рассылок. Короткие всплески также могут в итоге приводить к 429. Области действия различаются: распределение сообщений по чатам не отменяет общего ограничения бота.

Эти значения не гарантируют успех каждого запроса ниже порога. Важны правила конкретного метода и фактический ответ. Здесь рассматривается обычная очередь отправки, а не настройка платных рассылок и не все возможные причины 429.

Справочник Bot API определяет ResponseParameters.retry_after как необязательное целое число секунд, оставшихся до возможности повторить запрос после превышения flood control. Это поле JSON, а не универсальный контракт HTTP-заголовка. Отсутствие поля нельзя превращать в нулевую задержку.

Если воркеру нужен результат, выполняйте отдельный API-запрос. В сравнении ответа на webhook и отдельного sendMessage объясняется, почему при вложенном вызове метода в ответе webhook приложение не получает результат этого метода.

Постройте устойчивую очередь, а не цикл с sleep

Далее описаны рекомендации для приложения, а не дополнительные гарантии Telegram.

До отправки сохраните локальное задание для каждого намерения ответить: идентификатор бота, получателя, согласованный текст, правило истечения срока, число попыток и ближайшее разрешённое время. Это поля вашего хранилища, не дополнительные параметры Bot API. Устраняйте дубликаты бизнес-намерений, чтобы повторная доставка входящего обновления не создавала второе задание ответа.

Используйте три уровня допуска:

  1. Бот: координация суммарного трафика всех процессов с одной учётной записью бота.
  2. Чат: отдельное регулирование получателей, включая ограничение для групп.
  3. Задание: срок повторной попытки, актуальность, отмена и аренда права исполнения.

Захватывайте задания атомарно, чтобы два воркера не отправили одно и то же. Непосредственно перед сетевым вызовом повторно проверяйте общие ограничения. После ответа flood control атомарно продлите паузу и верните задание планировщику. Не держите входящий webhook открытым на время ожидания.

retry_after не сообщает, какой именно лимит сработал. Консервативная начальная политика — приостановить всю очередь отправки этого бота на указанный срок. Это задержит и другие чаты, зато не выдаёт предположение о локальном лимите чата за факт. Зафиксируйте это как собственную политику и уточняйте её по наблюдениям, а не по догадкам о сервере.

Преобразуйте retry_after в сохраняемый срок

Пример JavaScript показывает только классификацию результата. Он не отправляет запросы, не реализует базу данных и не является проверенным промышленным планировщиком. receivedAtMs — время получения ответа клиентом, currentNotBeforeMs — уже сохранённый общий срок ожидания.

function telegramFloodDecision(body, receivedAtMs, currentNotBeforeMs = 0) {
  if (body?.ok !== false || body.error_code !== 429) {
    return { action: 'classify_other_result' };
  }

  const seconds = body.parameters?.retry_after;
  if (!Number.isSafeInteger(seconds) || seconds < 0) {
    return { action: 'hold_for_review' };
  }

  const deadline = receivedAtMs + seconds * 1000;
  if (!Number.isSafeInteger(deadline)) {
    return { action: 'hold_for_review' };
  }

  return {
    action: 'reschedule',
    notBeforeMs: Math.max(currentNotBeforeMs, deadline)
  };
}

Названия возвращаемых значений принадлежат приложению. Сохраняйте максимальный срок транзакционно: обычное чтение с последующей незащищённой записью может затереть более длинную паузу другого воркера. Синхронизируйте часы процессов или используйте общий источник времени планировщика. Случайная добавка к задержке может только увеличивать ожидание, а не сокращать его.

Наступление срока разрешает попытку, но не гарантирует успех. Задайте предел числа попыток и возраста задания. Новый ответ может продлить паузу — сохраните новый срок. Если задание устарело или оператор уже ответил, отмените его либо отправьте на проверку. Эти пределы и правила отмены — бизнес-политика, не лимиты Telegram.

Разделяйте отказ, неопределённость и приём событий

Наблюдаемый результатРекомендуемое решение
Корректный ответ flood control с retry_afterСохранить паузу; повторить только после всех проверок допуска
429 без пригодного времени ожиданияПриостановить и классифицировать, не запускать немедленный цикл
Успешный sendMessageСохранить возвращённый Message, не ставить то же намерение заново
Тайм-аут или потерянный ответОтметить неизвестный результат; сверить или проверить до повтора
Другая ошибка APIОтдельно проверить права, адресата и корректность запроса

Локальный ID задания защищает от повторной постановки внутри приложения, но не гарантирует однократное выполнение на удалённой стороне. Сбой после успешной отправки, но до сохранения ответа всё ещё оставляет неопределённость. Один outbox не устраняет все возможные дубликаты сообщений.

Приём обновлений — отдельная граница. Проверьте подлинность и надёжно сохраните обновление, затем подтвердите приём независимо от задержанной отправки. Если сами обновления не приходят, используйте руководство по диагностике getWebhookInfo. pending_update_count считает обновления, ожидающие доставки, а не задания вашей исходящей очереди.

Не смешивайте контракт UnifyPort с Bot API

Неофициальный интерфейс UnifyPort работает с подключённым аккаунтом сообщений и использует другой контракт отправки текста. Оболочка ошибки содержит error.code, error.numeric_code, error.message и request_id для трассировки. Справочник ошибок не определяет поле Bot API parameters.retry_after; не переносите приведённый классификатор без изменений.

В многоканальном воркере хранилище заданий может быть общим, но классификаторы результатов должны учитывать конкретный контракт. Сохраняйте HTTP-статус и структурированные ошибки. Как объясняет руководство по трассировке, X-Request-Id не является документированным ключом идемпотентности отправки. Неофициальный интерфейс не отменяет ограничения исходной платформы и не делает повтор неопределённой отправки безопасным.

Отделяйте ошибки отправки и от статусов, которые возвращает ваш получатель webhook. По документации доставки UnifyPort повторы доставки выполняются немедленно и не учитывают Retry-After. Ответ 429 от вашего обработчика не планирует паузу в исходящей очереди.

Проверки перед запуском и FAQ

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

retry_after измеряется в секундах или миллисекундах?

В секундах. Явно преобразуйте значение, если планировщик хранит миллисекунды.

Поможет ли увеличение числа воркеров?

Само по себе — нет. У воркеров одного бота общие ограничения; нескоординированный параллелизм может увеличить число отказов.

Гарантирует ли ожидание успешную отправку?

Нет. Оно выполняет условие ожидания из конкретного ответа. Другие ограничения, права и последующие ответы API остаются значимыми.

Нужно ли так же повторять запрос после тайм-аута?

Нет. Тайм-аут без ответа — не подтверждённый отказ flood control; результат отправки может быть неизвестен.

Следующий шаг и источники

Добавьте сохраняемую паузу в существующий отправитель и проверьте границы сбоев до увеличения параллелизма. Для клиента подключённого аккаунта начните со справочника ошибок UnifyPort, а не с предположения о совместимости с Bot API.

Официальные материалы проверены 2026-10-18:

UnifyPort API

Превратите интеграцию сообщений в стабильный продуктовый pipeline.

Начните с отправки через единый API, затем возвращайте входящие сообщения в бизнес-систему стандартными событиями.