Telegram
8 мин·

Ошибка 429 в Telegram — причины и стратегии обхода

Почему Telegram возвращает 429 Too Many Requests и как правильно реализовать backoff, очереди и rate limiting.

Что такое ошибка 429

HTTP-статус 429 Too Many Requests — стандартный ответ сервера, означающий, что клиент отправил слишком много запросов за определённый период. В контексте Telegram Bot API это одна из самых частых ошибок при масштабировании ботов.

Когда бот превышает допустимую частоту запросов, Telegram возвращает JSON с описанием ошибки:

{
  "ok": false,
  "error_code": 429,
  "description": "Too Many Requests: retry after 5",
  "parameters": {
    "retry_after": 5
  }
}

Ключевое поле — parameters.retry_after. Оно указывает, через сколько секунд можно повторить запрос. Игнорирование этого поля — прямой путь к временной блокировке бота. Подробнее о всех лимитах Telegram читайте в статье Telegram API лимиты в 2026 году.

Типичные причины появления

Ошибка 429 возникает в разных сценариях. Вот наиболее частые:

СценарийПричинаРешение
Массовая рассылкаОтправка >30 сообщений/секRate limiter + очередь
Флуд в одну группу>20 сообщений/мин в один чатАгрегация сообщений
Частый pollinggetUpdates без таймаутаLong polling с timeout=30
Параллельные запросыНесколько воркеров без координацииЦентрализованный rate limiter
Редактирование сообщенийЧастое обновление progress barБатчинг обновлений (раз в 3–5 сек)
Множество ботов на одном IPIP-level rate limitingРаспределение по IP-адресам
Webhook + sendMessageОтвет в webhook + параллельная отправкаВыбрать один способ отправки
Частая ошибка: разработчики не учитывают, что лимит в 30 msg/sec — суммарный для всех операций бота. Если вы одновременно отправляете сообщения, редактируете и удаляете — все эти операции учитываются вместе.

Заголовок Retry-After

Telegram включает информацию о необходимой паузе в тело ответа (полеparameters.retry_after) и иногда в HTTP-заголовок Retry-After. Всегда используйте значение из тела ответа — оно более надёжно.

Типичные значения Retry-After:

  • 1–5 секунд — лёгкое превышение, обычно при рассылке
  • 10–30 секунд — серьёзное превышение или повторное нарушение
  • 60–300 секунд — бот попал под усиленный мониторинг
  • Более 300 секунд — риск временной блокировки

Правило простое: всегда уважайте Retry-After. Если вы продолжаете отправлять запросы во время паузы, Telegram увеличит время блокировки экспоненциально.

Экспоненциальный backoff

Экспоненциальный backoff — базовая стратегия обработки 429. Идея проста: при каждой повторной ошибке увеличиваем время ожидания в 2 раза.

// Экспоненциальный backoff для Telegram Bot API
async function sendWithBackoff(token, chatId, text, maxRetries = 5) {
  let baseDelay = 1000; // начальная задержка 1 сек

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const res = await fetch(
      `https://api.telegram.org/bot${token}/sendMessage`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ chat_id: chatId, text }),
      }
    );

    if (res.ok) {
      return await res.json();
    }

    if (res.status === 429) {
      const body = await res.json();
      const retryAfter = body.parameters?.retry_after || 1;
      // Берём максимум из retry_after и экспоненциальной задержки
      const delay = Math.max(retryAfter * 1000, baseDelay);
      // Добавляем jitter (±20%) для предотвращения «стада»
      const jitter = delay * (0.8 + Math.random() * 0.4);

      console.warn(
        `[429] Попытка ${attempt + 1}/${maxRetries}, retry_after=${retryAfter}s, ждём ${Math.round(jitter)}ms`
      );

      await new Promise((r) => setTimeout(r, jitter));
      baseDelay *= 2; // удваиваем базовую задержку
      continue;
    }

    // Другие ошибки — не ретраим
    throw new Error(`Telegram error: ${res.status}`);
  }

  throw new Error(`Не удалось отправить за ${maxRetries} попыток`);
}

Зачем нужен jitter

Без jitter (случайного разброса) все воркеры, получившие 429, попробуют повторить запрос одновременно — и снова получат 429. Это называется «thundering herd problem». Добавление ±20% случайного разброса решает проблему.

Очередь с повторной отправкой

Для production-систем экспоненциальный backoff недостаточен. Нужна полноценная очередь с управлением повторными попытками. Подробную архитектуру описываем в статье массовая отправка в Telegram.

// Простая очередь отправки с retry для Telegram
class TelegramSendQueue {
  constructor(token, rateLimit = 25) {
    this.token = token;
    this.rateLimit = rateLimit;
    this.queue = [];
    this.dlq = []; // Dead Letter Queue
    this.processing = false;
    this.stats = { sent: 0, retried: 0, failed: 0 };
  }

  add(chatId, text, maxRetries = 3) {
    this.queue.push({ chatId, text, retries: 0, maxRetries });
    if (!this.processing) this.process();
  }

  async process() {
    this.processing = true;
    const interval = 1000 / this.rateLimit;

    while (this.queue.length > 0) {
      const task = this.queue.shift();

      try {
        await this.send(task.chatId, task.text);
        this.stats.sent++;
      } catch (err) {
        if (err.retryAfter && task.retries < task.maxRetries) {
          task.retries++;
          this.stats.retried++;
          // Возвращаем в очередь с задержкой
          setTimeout(() => this.queue.push(task), err.retryAfter * 1000);
        } else {
          this.stats.failed++;
          this.dlq.push({ ...task, error: err.message, failedAt: new Date() });
        }
      }

      await new Promise((r) => setTimeout(r, interval));
    }
    this.processing = false;
  }

  async send(chatId, text) {
    const res = await fetch(
      `https://api.telegram.org/bot${this.token}/sendMessage`,
      {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ chat_id: chatId, text }),
      }
    );

    if (res.status === 429) {
      const body = await res.json();
      const err = new Error("Rate limited");
      err.retryAfter = body.parameters?.retry_after || 5;
      throw err;
    }

    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return res.json();
  }

  getStats() {
    return {
      ...this.stats,
      pending: this.queue.length,
      deadLetters: this.dlq.length,
    };
  }
}

// Использование
const queue = new TelegramSendQueue("BOT_TOKEN");
userIds.forEach((id) => queue.add(id, "Ваш заказ отправлен!"));
// Проверка статистики
setInterval(() => console.log(queue.getStats()), 5000);

Алгоритм Token Bucket

Token Bucket — элегантный способ контролировать скорость отправки. Идея: есть «ведро» с токенами, которое пополняется с фиксированной скоростью. Каждая отправка «тратит» один токен. Если токенов нет — ждём.

// Token Bucket rate limiter
class TokenBucket {
  constructor(capacity, refillRate) {
    this.capacity = capacity;     // макс. токенов
    this.tokens = capacity;       // текущие токены
    this.refillRate = refillRate; // токенов в секунду
    this.lastRefill = Date.now();
  }

  async acquire() {
    this.refill();

    if (this.tokens >= 1) {
      this.tokens -= 1;
      return true;
    }

    // Ждём пока появится токен
    const waitTime = (1 / this.refillRate) * 1000;
    await new Promise((r) => setTimeout(r, waitTime));
    this.refill();
    this.tokens -= 1;
    return true;
  }

  refill() {
    const now = Date.now();
    const elapsed = (now - this.lastRefill) / 1000;
    this.tokens = Math.min(
      this.capacity,
      this.tokens + elapsed * this.refillRate
    );
    this.lastRefill = now;
  }
}

// Использование: 25 токенов, пополнение 25/сек
const limiter = new TokenBucket(25, 25);

async function sendLimited(chatId, text) {
  await limiter.acquire(); // ждёт, если лимит исчерпан
  return sendTelegramMessage(chatId, text);
}
Совет: Token Bucket лучше подходит для плавного трафика. Для burst-трафика (пик при массовой рассылке) используйте комбинацию Token Bucket + очередь. В Релая именно такая архитектура используется по умолчанию.

Мониторинг ошибок 429

Без мониторинга вы не узнаете о проблемах, пока пользователи не начнут жаловаться. Вот ключевые показатели, которые нужно отслеживать:

ПоказательЧто показываетПорог алерта
429 errors / minuteЧастота rate limiting> 5/мин
Avg retry_afterСерьёзность превышения> 10 секунд
Queue depthРазмер очереди (backpressure)> 1000 сообщений
DLQ sizeНедоставленные сообщения> 0
Success rateПроцент успешных доставок< 99%
P95 delivery timeВремя доставки с учётом retry> 30 секунд
// Статистика доставки — пример dashboard API
const stats = await fetch('/api/stats/delivery');
// {
//   "requests_total": { "success": 25340, "rate_limited": 45 },
//   "retry_after_p95_sec": 3,
//   "queue_depth": 127,
//   "dlq_size": 2,
//   "delivery_duration_p95_sec": 1.2
// }

Настройте алерты в дашборде или аналогичной системе. Если процент 429 превышает 1% от общего количества запросов — пора пересмотреть архитектуру отправки.

Как Релая устраняет проблему 429

Релая полностью берёт на себя управление rate limiting для Telegram и других каналов. Вот как это работает:

  • Адаптивный rate limiter: платформа автоматически подстраивает скорость отправки под текущие лимиты Telegram, учитывая возраст бота и историю нарушений
  • Распределённая очередь: сообщения хранятся в надёжной очереди с гарантией доставки и повторными попытками
  • Автоматический backoff: при получении 429 платформа приостанавливает отправку на нужное время без потери сообщений
  • DLQ и алерты: если сообщение не удалось доставить после всех попыток, оно попадает в DLQ с уведомлением
  • Мультиканальное резервирование: при серьёзных ограничениях Telegram можно автоматически переключиться на MAX — где лимиты более предсказуемы
Результат: при использовании Релая вероятность столкнуться с 429 существенно снижается. Платформа проактивно управляет скоростью отправки, а при необходимости использует MAX как запасной канал — растущий мессенджер с лояльной российской аудиторией.

Если вы столкнулись с ошибкой 429 и хотите стабилизировать отправку, попробуйте Релая. Также можно прочитать наши статьи о лимитах Telegram API и массовой отправке без блокировок.

Создайте бесплатный MAX-профиль

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