Skip to content

Long Polling vs Webhook

Telegram Bot API предоставляет два способа получения обновлений для вашего бота: long polling и webhook. У каждого подхода есть свои компромиссы в плане разработки, производительности и масштабируемости.

🤖
Your Bot
📤getUpdates
✈️
Telegram
Bot constantly asks Telegram: "Any new updates?" — even if there are none.

Как работает Long Polling

При long polling ваш бот непрерывно вызывает метод getUpdates в цикле. Telegram удерживает соединение открытым, пока не появятся новые обновления (или не истечёт таймаут), а затем отвечает ими.

Bot                         Telegram
 │── getUpdates ──────────────►│
 │                             │ (ожидает обновлений...)
 │◄─────────── [update] ───────│
 │── getUpdates ──────────────►│
 │                             │ (ожидает...)
 │◄─────────── [] ─────────────│  ← нет обновлений
 │── getUpdates ──────────────►│
 ...
  • Ваш бот инициирует каждый запрос — Telegram никогда не обращается к вашему боту напрямую
  • Параметр timeout контролирует, как долго Telegram удерживает соединение (по умолчанию: 30, рекомендуется: 25–30 секунд)
  • Не требуется публичный URL или SSL-сертификат
  • Просто запускается локально — достаточно стартовать бота

В GramIO long polling используется по умолчанию:

ts
import { Bot } from "gramio";

const bot = new Bot(process.env.BOT_TOKEN as string);

bot.on("message", (context) => {
    return context.send("Hello!");
});

bot.start(); // ← используется long polling по умолчанию

Как работает Webhook

При использовании webhook вы регистрируете HTTPS URL в Telegram. Когда приходит обновление, Telegram отправляет HTTP POST запрос на этот URL с данными обновления.

Bot                         Telegram
 │                             │
 │    (простой — нет трафика)   │
 │                             │
 │                             │ ← пользователь отправил сообщение
 │◄──── POST /webhook ────────│
 │── 200 OK ──────────────────►│
 │                             │
 │    (простой — нет трафика)   │
  • Telegram пушит обновления вашему боту — цикл опроса не нужен
  • Требуется публично доступный HTTPS URL с валидным SSL-сертификатом
  • Меньшая задержка — обновления приходят мгновенно, без ожидания следующего цикла опроса
  • Более эффективно — нет трафика, когда обновлений нет

Что выбрать?

Разработка — Long Polling

Long polling идеален для локальной разработки:

  • Не нужен домен или SSL — работает на localhost из коробки
  • Без конфигурации — просто вызовите bot.start()
  • Простая отладка — перезапуск бота мгновенный, не нужно настраивать туннели
  • Нет проблем с файрволом — только исходящие соединения, не нужно открывать входящие порты

Продакшен — Webhook

Webhook — рекомендуемый выбор для продакшен-развёртываний:

  • Меньшая задержка — обновления доставляются мгновенно, без задержки опроса
  • Нет холостых соединений — ресурсы не тратятся, когда обновлений нет
  • Горизонтальное масштабирование — несколько экземпляров сервера за балансировщиком нагрузки
  • Совместимость с serverless — работает с Vercel, AWS Lambda, Cloudflare Workers и аналогичными платформами
  • Эффективность ресурсов — нет фонового цикла опроса, потребляющего CPU и память

Несколько подов и масштабирование

Здесь выбор становится критически важным.

Long Polling: только один экземпляр

Telegram отклоняет одновременные вызовы getUpdates от одного токена бота. Если два процесса опрашивают одновременно, один получает ошибку 409 Conflict. Это означает:

  • Можно запускать только один экземпляр с polling
  • Нет горизонтального масштабирования
  • Нет реплик в Kubernetes, нет сервисов Docker Swarm с replicas > 1
  • Если единственный процесс упадёт, обновления задерживаются до его перезапуска

Webhook: горизонтальное масштабирование

При webhook Telegram отправляет обновления на URL — ему всё равно, сколько серверов за ним стоит. Это открывает:

                  ┌─── Pod 1 (bot) ◄──┐
Telegram ──POST──►│ Load Balancer      │
                  ├─── Pod 2 (bot) ◄──┤
                  └─── Pod 3 (bot) ◄──┘
  • Балансировщик нагрузки распределяет входящие запросы по подам
  • Каждый под обрабатывает часть обновлений независимо
  • Автомасштабирование работает естественно — добавляйте поды по мере роста нагрузки
  • Бесшовные деплои — обновление подов без простоя (rolling updates)

WARNING

При масштабировании с webhook убедитесь, что логика вашего бота не хранит состояние локально (или используйте общее хранилище, например Redis, для сессий). Каждое обновление может прийти на другой под.

Сравнительная таблица

ХарактеристикаLong PollingWebhook
Сложность настройкиМинимальнаяТребуется HTTPS URL + SSL
Нужен публичный URLНетДа
ЗадержкаЗависит от интервала опросаМгновенная доставка
Ресурсы в простоеПостоянные (цикл опроса)Нулевые
Горизонтальное масштабированиеНет (один экземпляр)Да (балансировщик)
Поддержка serverlessНетДа
Локальная разработкаЛегкоТребуется туннель
Проблемы с файрволомНет (только исходящие)Нужен входящий HTTPS

Настройка Webhook в GramIO

Поддерживаемые фреймворки

Пример

ts
import { Bot, webhookHandler } from "gramio";
import Fastify from "fastify";

const bot = new Bot(process.env.BOT_TOKEN as string);
const fastify = Fastify();

fastify.post("/telegram-webhook", webhookHandler(bot, "fastify"));

fastify.listen({ port: 3445, host: "::" });

bot.on("message", (context) => {
    return context.send("Fastify!");
});

bot.start({
    webhook: {
        url: "https://example.com:3445/telegram-webhook",
    },
});

Использование вебхуков только в продакшене

Вместо использования туннелей вы можете просто использовать polling в среде разработки!

ts
const bot = new Bot(process.env.BOT_TOKEN);

await bot.start({
    webhook:
        process.env.NODE_ENV === "production"
            ? {
                  url: `${process.env.API_URL}/${process.env.BOT_TOKEN}`,
              }
            : undefined,
});

Когда webhook равен undefined, GramIO автоматически переключается на long polling.

Локальная разработка с вебхуками

Для локальной разработки с вебхуками мы рекомендуем использовать untun Logounjs/untun.

Untun — это инструмент для создания туннеля между вашим локальным HTTP(s) сервером и внешним миром!

IMPORTANT

Примеры запуска с конкретными фреймворками опущены. Смотрите этот пример.

Через API

Этот метод позволяет установить ссылку на наш туннель непосредственно в скрипте.

Установите пакет:

::: pm-add untun :::

Запустите туннель и установите вебхук:

ts
import { startTunnel } from "untun";

const tunnel = await startTunnel({ port: 3000 });

bot.start({
    webhook: {
        url: await tunnel.getURL(),
    },
});

Через CLI

Мы прослушиваем порт 3000 локально. Поэтому открываем туннель следующим образом:

sh
npx untun@latest tunnel http://localhost:3000
sh
yarn dlx untun@latest tunnel http://localhost:3000
sh
pnpx untun@latest tunnel http://localhost:3000
sh
bunx untun@latest tunnel http://localhost:3000
bash
 Starting cloudflared tunnel to http://localhost:3000
 Waiting for tunnel URL...
 Tunnel ready at https://unjs-is-awesome.trycloudflare.com

Теперь мы используем эту ссылку при установке вебхука:

ts
bot.start({
    webhook: {
        url: "https://unjs-is-awesome.trycloudflare.com",
    },
});

Напишите свой собственный обработчик обновлений

ts
// не существующий фреймворк для примера
import { App } from "some-http-framework";
import { Bot } from "gramio";

const bot = new Bot(process.env.BOT_TOKEN as string).on("message", (context) =>
    context.send("Hello!")
);

// init обязателен. Он используется для ленивой загрузки плагинов, а также получает информацию о боте.
await bot.init();

const app = new App().post("/telegram", async (req) => {
    // req.body должен быть json эквивалентом TelegramUpdate
    await bot.updates.handleUpdate(req.body);
});

app.listen(80);