Как устроен Bot API: polling vs webhook

Когда ты нажал «Запустить» в прошлом уроке и Цыплёнок ответил на /start — между твоим сообщением и его ответом произошёл маленький фокус. Сейчас мы его раскроем.

Главная мысль урока: бот не сидит в Telegram. Он живёт на твоём компьютере, а сообщения от пользователей до него надо как-то довезти. Есть два способа доставки: бот сам бегает за новостями (polling) или Telegram приносит их ему сам (webhook). От выбора зависит, нужен тебе сервер с белым адресом или хватит ноутбука под столом.

Зачем тебе вообще это знать

В прошлом уроке ты собрал самого первого Цыплёнка и запустил его. Скорее всего, в коде была строчка вроде dp.run_polling(bot), и ты не особо задумывался, что это значит — главное, что работало. Так вот, в этом слове polling прячется целый механизм, и без понимания его легко наступить на грабли, на которые наступают вообще все новички.

Типичная история: ты написал бота, всё классно, выложил код на GitHub, чтобы похвастаться другу. А через час тебе приходит письмо от Telegram: «ваш токен скомпрометирован, мы его отозвали». Или другое: ты запускаешь второго бота с тем же токеном, и оба начинают сходить с ума — отвечают через раз, теряют сообщения. Или третье: ты прочитал в умной статье слово webhook, попробовал его включить, и бот замолчал намертво. Все три беды растут из одного корня — из непонимания, как именно бот получает сообщения.

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

Сначала разберёмся, что такое обновление (update)

Update (обновление) — единица входящих данных от Telegram: новое сообщение, нажатая кнопка или другое событие.

Представь школьный ящик для записок у входа в класс. Каждый раз, когда кто-то хочет тебе что-то сказать, он не кричит на весь коридор — он пишет записку и кидает её в ящик. Записка про домашку, записка «выйди на перемене», записка с мемом — всё это разные записки, но лежат они в одном ящике, по порядку.

У Telegram для твоего бота тоже есть такой ящик. Только записки в нём называются обновлениями (по-английски update). Кто-то написал боту «привет» — в ящик упало обновление. Кто-то нажал кнопку под сообщением — ещё одно обновление. Кто-то добавил бота в группу — снова обновление. Telegram аккуратно складывает их по порядку и нумерует, чтобы ничего не потерялось и не перепуталось.

Важно понять вот что: Telegram не знает, где живёт твой бот, и сам стучаться к нему не будет (по умолчанию). Он просто копит обновления у себя на сервере и ждёт, когда их заберут. Это как почтовое отделение: посылка пришла, лежит на полке, но почтальон к тебе домой её не понесёт, пока ты сам не оставишь заявку на доставку.

Способ первый: polling — бот сам бегает за новостями

Polling — способ получать обновления, при котором бот сам регулярно спрашивает у Telegram «есть ли что-то новое?».

Вспомни, как ты ждёшь ответа на важное сообщение в мессенджере. Телефон вроде должен звякнуть сам, но ты всё равно каждые тридцать секунд разблокируешь экран и проверяешь: пришло? пришло? а сейчас? Вот это и есть polling — бот ведёт себя ровно так же.

Слово poll по-английски значит «опрашивать». Бот в режиме polling работает по кругу:

  1. Бот сам подключается к серверам Telegram и спрашивает: «Привет, для меня есть новые обновления?»
  2. Если есть — Telegram отдаёт их пачкой, бот разбирает каждое и зовёт нужный хэндлер.
  3. Если новостей нет — Telegram просто немного держит соединение и через время отвечает «пока пусто».
  4. Бот тут же спрашивает снова. И снова. И снова — пока работает программа.

Главная прелесть в том, что инициатива всегда у бота. Это значит, что боту не нужен ни белый IP-адрес, ни домен, ни сертификат — вообще ничего, кроме доступа в интернет. Он сам исходящим соединением дотягивается до Telegram, как ты сам открываешь сайт в браузере. Поэтому polling прекрасно работает с ноутбука, из дома, из школьного компьютера — откуда угодно.

Как это выглядит в коде Цыплёнка

Открой свой bot.py из прошлого урока. Внизу почти наверняка есть что-то такое:

import asyncio
from aiogram import Bot, Dispatcher
from aiogram.filters import CommandStart
from aiogram.types import Message
import os

bot = Bot(token=os.getenv("BOT_TOKEN"))
dp = Dispatcher()


@dp.message(CommandStart())
async def start_handler(message: Message):
    await message.answer("Привет! Я Цыплёнок-помощник 🐥")


async def main():
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

Результат: в чате бот ответит «Привет! Я Цыплёнок-помощник» на команду /start. Строка dp.start_polling(bot) запускает тот самый бесконечный круг опроса: пока программа в терминале не остановлена, Цыплёнок снова и снова спрашивает Telegram про новые обновления и раздаёт их хэндлерам.

Разберём по шагам, что тут происходит:

  • bot = Bot(token=...) — создаём объект Bot, который умеет ходить в Telegram. Токен берём не из кода напрямую, а из переменной окружения BOT_TOKEN — почему так, скоро поймёшь.
  • dp = Dispatcher() — создаём Dispatcher, диспетчера. Это он будет разбирать прилетающие обновления и решать, какому хэндлеру их отдать.
  • dp.start_polling(bot) — говорим диспетчеру: «запускай цикл опроса, забирай обновления через объект bot и раздавай их моим хэндлерам». Эта строка не возвращает управление — она крутится, пока ты не нажмёшь Ctrl+C.

Заметь: нигде нет ни адреса сервера, ни порта, ни сертификата. Боту это и не нужно — он сам инициатор связи.

Способ второй: webhook — Telegram сам приносит новости

Webhook — способ получать обновления, при котором Telegram сам присылает их на адрес твоего сервера.

А теперь представь, что ты не дёргаешь телефон каждые полминуты, а просто включил пуш-уведомления. Пришло сообщение — телефон сам звякнул. Не пришло — он молчит и не тратит твоё внимание. Вот это webhook.

В этом режиме инициатива переворачивается: теперь стучится Telegram, а не бот. Ты один раз говоришь Telegram: «Слушай, когда для моего бота появится обновление, не жди, пока я приду, — сразу отправь его вот на этот адрес: https://мой-сервер.ру/webhook». Telegram запоминает адрес. И как только кто-то пишет боту, Telegram мгновенно делает HTTP-запрос на твой сервер с этим обновлением внутри.

Звучит удобнее, да? Никаких бесконечных «а сейчас? а сейчас?». Но за удобство надо платить, и плата серьёзная для новичка: у тебя должен быть сервер, до которого Telegram сможет дотянуться из интернета. А это значит:

  • публичный адрес (домен или белый IP), который видно из внешнего мира — ноутбук под столом так просто не подходит;
  • обязательно HTTPS — Telegram отказывается слать обновления на незащищённое соединение;
  • работающая веб-программа, которая принимает входящие запросы и не падает.

Как это выглядит в коде

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

from aiohttp import web
from aiogram import Bot, Dispatcher
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application
import os

bot = Bot(token=os.getenv("BOT_TOKEN"))
dp = Dispatcher()

WEBHOOK_PATH = "/webhook"
WEBHOOK_URL = "https://moy-server.ru" + WEBHOOK_PATH


async def on_startup(app):
    # один раз говорим Telegram, куда слать обновления
    await bot.set_webhook(WEBHOOK_URL)


app = web.Application()
SimpleRequestHandler(dispatcher=dp, bot=bot).register(app, path=WEBHOOK_PATH)
setup_application(app, dp, bot=bot)
app.on_startup.append(on_startup)

if __name__ == "__main__":
    web.run_app(app, host="0.0.0.0", port=8080)

Результат: бот будет вести себя для пользователя точно так же — ответит на /start «Привет, я Цыплёнок». Но под капотом всё иначе: вместо цикла опроса поднимается маленький веб-сервер на aiohttp, который слушает порт 8080 и ждёт, пока Telegram сам постучится на адрес /webhook с новым обновлением. Строка bot.set_webhook(...) — это и есть та самая «заявка на доставку», которую бот один раз оставляет в Telegram.

Обрати внимание на главное отличие: тут есть host, port, WEBHOOK_URL — целый адрес. В polling-версии этого не было вообще. Это и есть та цена, о которой я говорил.

Сравним лоб в лоб

Чтобы всё уложилось, держи табличку. Перечитывай её, когда будешь выбирать способ для своего проекта.

ВопросPollingWebhook
Кто кому стучится?Бот стучится в TelegramTelegram стучится боту
Нужен публичный адрес и HTTPS?НетДа, обязательно
Запустится с ноутбука дома?Да, без настроекНет (нужен сервер)
Реакция на сообщениеС маленькой задержкой (раунд опроса)Практически мгновенно
Нагрузка при тысячах пользователейБот постоянно тратит соединенияЗапрос приходит только когда есть что доставить
Сложность настройкиОдна строка кодаСервер, домен, сертификат, веб-приложение

Маленький разбор: как Telegram нумерует обновления

Помнишь, я сказал, что обновления пронумерованы по порядку? У каждого есть число update_id. Когда бот в режиме polling забирает пачку обновлений, он запоминает самый большой номер и в следующий раз просит «всё, что новее этого». Так Telegram понимает, какие записки ты уже забрал, и больше их не отдаёт.

Чтобы это не звучало абстрактно, давай чисто на Python смоделируем эту логику маленьким сниппетом — без всякого Telegram, просто чтобы прочувствовать идею «отдавай только то, что новее»:

# Telegram держит обновления как список с номерами
inbox = [
    {"update_id": 101, "text": "привет"},
    {"update_id": 102, "text": "как дела"},
    {"update_id": 103, "text": "/start"},
]

# Бот уже забирал всё до 101 включительно
last_seen = 101

# Просим только то, что новее last_seen
new_updates = [u for u in inbox if u["update_id"] > last_seen]

for u in new_updates:
    print(u["update_id"], "->", u["text"])
    last_seen = u["update_id"]

print("Теперь last_seen =", last_seen)

Вывод:

102 -> как дела
103 -> /start
Теперь last_seen = 103

Видишь? Обновление 101 бот пропустил — он его уже видел. Забрал только 102 и 103 и сдвинул свою метку. В реальности aiogram делает ровно это за тебя, ты эту кухню даже не замечаешь. Но теперь ты знаешь, что внутри.

Частые ошибки и подводные камни

1. Два бота с одним токеном в режиме polling

Самая частая беда. Ты запустил Цыплёнка на ноутбуке, забыл выключить, пришёл домой и запустил его же на домашнем компьютере. Теперь два процесса дерутся за один ящик обновлений. Telegram отдаёт каждое обновление только кому-то одному, поэтому половина сообщений уходит одному боту, половина — другому, и оба будто «глючат». Telegram даже честно ругается в логах: terminated by other getUpdates request. Правило простое: один токен — один запущенный polling одновременно.

2. Попытка включить webhook без настоящего сервера

Прочитал, что webhook «быстрее и круче», вызвал bot.set_webhook("https://localhost/webhook") с ноутбука — и бот замолчал. А он и не мог заработать: localhost виден только тебе, Telegram до него не дотянется. Пока у тебя нет публичного домена с HTTPS, webhook просто не для тебя. Это нормально — почти все начинают с polling.

3. Включил webhook — и polling перестал работать (и наоборот)

Это не баг, это правило: у бота может быть активен только один способ доставки. Если ты раньше ставил webhook, а теперь хочешь снова polling, надо сначала его снять — у объекта bot для этого есть delete_webhook(). Иначе Telegram продолжает думать, что обновления надо слать на старый адрес, и твой start_polling получает пустоту.

4. Токен прямо в коде на GitHub

Токен — секретный ключ вида 123456:ABC..., который Telegram выдаёт боту и по которому отличает его от других; его нельзя публиковать.

Заметил, что в обоих примерах токен берётся через os.getenv("BOT_TOKEN"), а не вписан строкой? Это не случайно. Токен — как пароль от твоего аккаунта: кто им завладел, тот управляет ботом. Если ты впишешь токен прямо в код и выложишь репозиторий, специальные роботы найдут его за минуты. Поэтому секреты держат в переменных окружения — отдельно от кода. Подробно мы это настроим в модуле про деплой, а пока просто привыкай к привычке: токен в коде словом не пишем.

5. Ждёшь от polling мгновенной реакции

Polling опрашивает Telegram циклами, поэтому крошечная задержка между «нажал» и «бот ответил» — это норма, а не поломка. Для бота-напоминалки про домашку или опросника для друзей такая задержка вообще незаметна. Гнаться за webhook ради «миллисекунд» на старте смысла нет.

Мини-практика

Теорию закрепляем руками и головой. Сделай три вещи:

  1. Найди слово polling в своём коде. Открой bot.py и убедись, что там есть dp.start_polling(bot) (или dp.run_polling(bot)). Это и есть способ доставки, на котором сейчас живёт твой Цыплёнок.
  2. Спровоцируй ошибку «двух ботов» специально. Запусти Цыплёнка в одном терминале, а потом во втором терминале запусти его же ещё раз тем же токеном. Понаблюдай, как Telegram начинает ругаться terminated by other getUpdates. Поймёшь беду номер один на своей шкуре — и больше никогда на неё не попадёшься. Потом закрой второй процесс.
  3. Ответь себе письменно (можно в комментарии в коде): «Мой бот живёт у меня на ноутбуке, у меня нет домена и сервера. Какой способ доставки мне подходит — polling или webhook? Почему?» Если ответил «polling, потому что не нужен публичный адрес» — ты усвоил главное.

Со звёздочкой: загляни в документацию aiogram 3.x и найди метод bot.get_webhook_info(). Подумай, зачем он может пригодиться, когда ты не уверен, в каком режиме сейчас твой бот. (Подсказка: он покажет, висит ли на боте webhook-адрес.)

Итоги

Давай соберём всё в три коротких мысли, которые надо унести с собой:

  • Обновление (update) — это любая «записка» от Telegram боту: сообщение, нажатие кнопки, добавление в группу. Telegram копит их у себя и нумерует.
  • Polling — бот сам бегает за обновлениями. Просто, работает с ноутбука, не нужен сервер. Это твой выбор на весь курс, кроме самого финала.
  • Webhook — Telegram сам приносит обновления на твой сервер. Быстрее и экономнее под нагрузкой, но требует домена, HTTPS и работающего веб-приложения. Подключим в модуле про деплой.

Теперь ты понимаешь, что прячется за невинной строчкой start_polling, и не испугаешься, когда встретишь слово webhook в чужом коде. А в следующих уроках мы перестанем заставлять Цыплёнка отвечать одной фразой и научим его показывать кнопки и меню — чтобы с ним было удобно общаться даже тем, кто не знает ни одной команды. Поехали дальше 🐥

Проверьте себя
1. Что такое обновление (update) в Telegram Bot API?
AНовая версия библиотеки aiogram
BЕдиница входящих данных от Telegram: сообщение, нажатие кнопки или другое событие
CКоманда, которой обновляют токен бота
DОтвет, который бот отправляет пользователю
2. Чем polling отличается от webhook по тому, кто кому стучится?
AПри polling Telegram стучится боту, при webhook бот стучится Telegram
BВ обоих случаях стучится только бот
CПри polling бот сам спрашивает Telegram про новости, при webhook Telegram сам присылает их на сервер бота
DВ обоих случаях стучится только Telegram
3. Почему polling удобнее для новичка, который запускает бота с домашнего ноутбука?
AПотому что polling работает быстрее webhook
BПотому что боту не нужен ни публичный адрес, ни домен, ни HTTPS — он сам исходящим соединением дотягивается до Telegram
CПотому что polling не требует токена
DПотому что при polling не нужен интернет
4. Что произойдёт, если запустить двух ботов с одним и тем же токеном в режиме polling?
AБоты будут работать в два раза быстрее
BTelegram автоматически объединит их в одного
CОни начнут делить один ящик обновлений и оба будут терять сообщения, а Telegram выдаст ошибку terminated by other getUpdates
DВторой бот просто не запустится и ничего не сломает
5. Что обязательно нужно для работы webhook, но не нужно для polling?
AПубличный адрес (домен или белый IP) и HTTPS, до которых Telegram дотянется из интернета
BУстановленная библиотека aiogram
CДействующий токен от BotFather
DХотя бы один написанный хэндлер
6. Почему в примерах кода токен берётся через os.getenv("BOT_TOKEN"), а не вписан строкой прямо в код?
AТак код работает быстрее
BТокен — это секрет вроде пароля; если вписать его в код и выложить репозиторий, его быстро украдут, поэтому секреты держат в переменных окружения
Caiogram не умеет принимать токен строкой
DТак Telegram выдаёт боту больше прав