Webhook и хостинг

Учим Telegram сам стучаться к нашему боту, а не наоборот — переводим «Цыплёнка-помощника» с polling на webhook и поднимаем для него настоящий веб-сервер.
Webhook — это способ получать обновления, при котором Telegram сам присылает их на адрес твоего сервера, как только что-то происходит. Боту больше не нужно постоянно спрашивать «есть что-нибудь новенькое?» — Telegram звонит в дверь сам.

Зачем вообще что-то менять, если бот и так работает?

Представь: ты запустил «Цыплёнка-помощника» на ноутбуке, друзья из класса написали ему /start, он ответил. Всё круто. А теперь закрой крышку ноута — и бот замолчал. Потому что пока он работал через polling, он жил прямо в твоём терминале: бесконечно бегал к Telegram и спрашивал «есть новые сообщения? а сейчас? а сейчас?». Выключил ноут — спрашивать стало некому.

Если ты хочешь, чтобы бот отвечал друзьям в три часа ночи, когда ты спишь, его надо переселить туда, где компьютер не выключается никогда — на хостинг (арендованный сервер в интернете). А заодно стоит научить его новому способу получать сообщения — webhook. Это как раз тот момент, когда любительский бот превращается в настоящий сервис, работающий 24/7.

К концу урока твой бот будет принимать обновления не через бесконечные вопросы, а так: Telegram сам присылает каждое новое сообщение на твой адрес, а маленький веб-сервер на aiohttp ловит его и передаёт в уже знакомый Dispatcher. Хэндлеры при этом не меняются вообще — вся магия в том, как сообщения попадают внутрь.

И ещё один приятный бонус, который многие новички недооценивают: webhook почти всегда быстрее. Пока polling-бот ждёт следующего «обхода» Telegram (а это доли секунды, но они складываются), webhook-бот получает сообщение ровно в тот миг, когда оно появилось. Для бота-напоминалки про домашку или для бота в чате игрового клана, где важно ответить мгновенно на нажатие кнопки, эта разница ощутима. Так что переход — это не только про «работает 24/7», но и про «отвечает быстрее».

Polling против webhook: официант и звонок в дверь

В прошлом уроке мы уже разбирали разницу между этими подходами (polling vs webhook). Освежим картинку короткой метафорой, потому что сейчас она станет очень важной.

Polling — это как нетерпеливый младший брат, который каждые две секунды бегает на кухню и спрашивает у мамы: «Еда готова? А сейчас? А сейчас?». Он тратит силы на вопросы, даже когда ответа нет. Зато ему не нужен ни звонок, ни адрес — он сам ходит к источнику.

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

ПараметрPollingWebhook
Кто проявляет инициативуБот сам спрашивает TelegramTelegram сам присылает боту
Нужен публичный адресНетДа, обязательно
Нужен HTTPSНетДа, обязательно
Где удобно запускатьНа ноуте, для разработкиНа сервере, для боевого режима
Задержка ответаЧуть больше (ждёт следующего опроса)Минимальная (прилетает сразу)
Сложность настройкиОдна строчкаСервер плюс домен плюс сертификат

Когда оставаться на polling — это нормально

Не думай, что webhook «круче» и polling — для новичков. Это просто разные инструменты. Пока ты пишешь и тестируешь бота на своём компьютере — polling идеален: запустил bot.py, проверил, остановил. Никаких доменов и серверов. Переходить на webhook стоит только тогда, когда у тебя появились две вещи: сервер, который работает круглосуточно, и публичный адрес с HTTPS, по которому Telegram сможет до этого сервера достучаться.

Что нужно, чтобы Telegram смог постучаться

Webhook работает только при двух условиях, и без них он просто откажется включаться. Разберём оба по-человечески.

1. Публичный адрес

Твой ноутбук в сети дома спрятан за роутером — у него нет адреса, на который можно зайти из любой точки интернета. Это как квартира без таблички на двери: курьер до тебя не дойдёт, потому что не знает, куда нести. Публичный адрес — это домен вроде mybot.example.com или внешний IP сервера, который видно отовсюду. Его дают хостинги: арендуешь сервер — получаешь адрес.

2. HTTPS (шифрование)

Telegram наотрез отказывается присылать обновления на обычный http://. Только https:// — то есть по защищённому, зашифрованному каналу. Логика простая: в сообщениях могут быть личные данные пользователей, и Telegram не хочет, чтобы их кто-то подсмотрел по дороге. HTTPS — это как запечатанный конверт вместо открытки: содержимое видно только отправителю и получателю.

Для HTTPS нужен SSL-сертификат — цифровая «печать», подтверждающая, что домен настоящий. Хорошая новость: бесплатные сертификаты раздаёт Let's Encrypt, а на многих хостингах (и в туннелях вроде ngrok, о котором ниже) HTTPS уже включён из коробки — тебе вообще ничего не надо делать руками.

3. Открытый порт

Telegram стучится на стандартные порты для HTTPS: 443, 80, 88 или 8443. Если твой сервер слушает какой-то другой порт — Telegram до него не дойдёт. Обычно перед ботом ставят nginx, который принимает запросы на 443 и пробрасывает их твоему боту на внутренний порт. Но для первых шагов хватит и того, что бот слушает один из разрешённых портов напрямую.

Настраиваем webhook через set_webhook

Сама команда «эй, Telegram, присылай обновления сюда» — это один вызов метода set_webhook у объекта bot. Помнишь, мы храним токен в переменной окружения (переменные окружения и секреты)? Адрес сервера тоже логично хранить там же — он у каждого свой.

import os
from aiogram import Bot

BOT_TOKEN = os.getenv("BOT_TOKEN")
# Полный адрес, по которому Telegram будет стучаться
WEBHOOK_HOST = os.getenv("WEBHOOK_HOST")   # например https://mybot.example.com
WEBHOOK_PATH = "/webhook"                    # путь внутри нашего сервера
WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}"

bot = Bot(token=BOT_TOKEN)

async def on_startup():
    # Говорим Telegram: присылай все обновления на этот адрес
    await bot.set_webhook(WEBHOOK_URL)

Результат: после вызова set_webhook Telegram запомнит твой адрес и начнёт присылать на https://mybot.example.com/webhook каждое новое сообщение боту. В терминале никаких ответов не будет — теперь всё происходит на сервере.

Разберём по строчкам. WEBHOOK_HOST — это твой публичный адрес с https://. WEBHOOK_PATH — придуманный нами кусок пути; можно поставить любой, но удобно сделать его «секретным», чтобы случайные запросы не сыпались на бота. WEBHOOK_URL склеивает их в полный адрес, который мы и отдаём Telegram.

Зачем удалять старый webhook

Вот важная тонкость: нельзя одновременно использовать polling и webhook. Если ты раньше запускал бота через start_polling, а потом включил webhook (или наоборот), Telegram запутается. Поэтому при переключении на polling в коде для локальной разработки полезно сбрасывать webhook:

async def on_startup_polling():
    # Снимаем webhook, чтобы можно было снова опрашивать вручную
    await bot.delete_webhook(drop_pending_updates=True)

Результат: Telegram забудет адрес сервера и снова позволит боту опрашивать обновления через polling. Параметр drop_pending_updates=True заодно выбросит сообщения, накопившиеся, пока бот спал, — удобно, чтобы он не отвечал на вчерашние «приветы».

Поднимаем aiohttp-сервер для приёма обновлений

Команды set_webhook мало: мы сказали Telegram, куда нести сообщения, но пока некому их ловить. Нужен маленький веб-сервер, который будет сидеть по адресу /webhook и принимать входящие запросы. В aiogram 3.x для этого есть готовая интеграция с aiohttp — асинхронным веб-сервером на Python. Тебе не придётся писать сервер с нуля: aiogram даёт специальный «обработчик запросов», который сам разбирает пришедший update и отдаёт его в Dispatcher.

Метафора: aiohttp-сервер — это домофон у подъезда. Telegram (курьер) звонит в домофон по адресу /webhook, домофон принимает посылку и передаёт её консьержу — нашему Dispatcher, который уже знает, в какую квартиру (хэндлер) её отнести.

import os
import asyncio
from aiogram import Bot, Dispatcher
from aiogram.filters import CommandStart
from aiogram.types import Message
from aiohttp import web
from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application

BOT_TOKEN = os.getenv("BOT_TOKEN")
WEBHOOK_HOST = os.getenv("WEBHOOK_HOST")
WEBHOOK_PATH = "/webhook"
WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}"
WEB_SERVER_HOST = "0.0.0.0"
WEB_SERVER_PORT = 8443

bot = Bot(token=BOT_TOKEN)
dp = Dispatcher()

@dp.message(CommandStart())
async def start_handler(message: Message):
    await message.answer("Привет! Я Цыплёнок-помощник и теперь живу на сервере 24/7")

async def on_startup(app: web.Application):
    await bot.set_webhook(WEBHOOK_URL)

def main():
    # Создаём веб-приложение aiohttp
    app = web.Application()
    # Регистрируем обработчик: все запросы на /webhook идут в Dispatcher
    SimpleRequestHandler(dispatcher=dp, bot=bot).register(app, path=WEBHOOK_PATH)
    # Связываем aiogram с aiohttp и вешаем set_webhook на старт сервера
    setup_application(app, dp, bot=bot)
    app.on_startup.append(on_startup)
    # Запускаем сервер
    web.run_app(app, host=WEB_SERVER_HOST, port=WEB_SERVER_PORT)

if __name__ == "__main__":
    main()

Результат: в чате бот ответит «Привет! Я Цыплёнок-помощник и теперь живу на сервере 24/7» на команду /start — но ответит он, только если сервер реально доступен по WEBHOOK_HOST через HTTPS. Сервер поднимается на порту 8443 и ждёт, когда Telegram постучится на /webhook.

Разберём ключевые места:

  • web.Application() — создаёт пустое веб-приложение, наш «домофон».
  • SimpleRequestHandler(...).register(app, path=WEBHOOK_PATH) — вешает на адрес /webhook обработчик, который принимает update от Telegram и кладёт его в dp.
  • setup_application(app, dp, bot=bot) — связывает aiogram и aiohttp в единое целое, чтобы startup и shutdown работали правильно.
  • web.run_app(...) — запускает сервер. Это и есть аналог start_polling, только теперь мы не опрашиваем, а ждём входящих.

Обрати внимание: хэндлер start_handler не изменился ни на букву. Логика бота полностью отделена от способа доставки сообщений. В этом и сила: завтра захочешь вернуться на polling — поменяешь только «обвязку» вокруг dp, а сами обработчики останутся как есть.

Как проверить webhook, не покупая сервер: ngrok

«Звучит здорово, но у меня нет ни домена, ни сервера» — частая мысль. И тут спасает ngrok — программа, которая делает временный публичный HTTPS-адрес для твоего ноутбука. Она пробивает «туннель» из интернета прямо к порту на твоей машине. Идеально, чтобы один раз вживую увидеть, как работает webhook, не тратя денег.

Запускаешь бота локально на порту 8443, потом в отдельном терминале — ngrok http 8443. ngrok выдаст адрес вроде https://abc123.ngrok-free.app. Этот адрес ты и подставляешь в WEBHOOK_HOST. Минус один: при перезапуске ngrok адрес меняется, и set_webhook нужно вызывать заново. Для боевого бота ngrok не годится, но для «потрогать руками» — лучшее, что есть.

Почему ngrok так удобен именно на этом этапе? Потому что он снимает сразу обе главные преграды: даёт публичный адрес (тот самый, который видно из любой точки интернета) и сразу с готовым HTTPS — сертификат уже встроен, тебе не нужно ничего настраивать. По сути ngrok временно превращает твой ноутбук в маленький сервер, до которого Telegram реально может достучаться. Когда ты впервые увидишь, как бот отвечает через webhook, запущенный прямо с твоей машины через туннель, — webhook перестанет казаться чем-то страшным и далёким. А уже потом, разобравшись на ngrok, переезжать на настоящий сервер будет совсем не страшно: принцип-то ровно тот же, меняется только адрес.

Маленький разбор: как выглядит update от Telegram

Чтобы webhook перестал быть магией, полезно увидеть, что именно прилетает на твой сервер. Telegram присылает JSON. Вот упрощённый разбор такого объекта на чистом Python — этот сниппет можно запустить прямо тут:

update = {
    "update_id": 100500,
    "message": {
        "text": "/start",
        "from": {"first_name": "Аня"},
    },
}

# Достаём то, что нужно хэндлеру
text = update["message"]["text"]
name = update["message"]["from"]["first_name"]
print(f"Пользователь {name} прислал команду: {text}")

Вывод:

Пользователь Аня прислал команду: /start

Именно такой словарь Telegram упаковывает в HTTP-запрос и шлёт на /webhook. Дальше aiogram сам превращает его в удобный объект Message, который ты уже сто раз использовал в хэндлерах. Тебе разбирать этот JSON вручную не придётся — но теперь ты знаешь, что внутри.

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

Webhook ломается у новичков почти всегда по одной из этих причин. Пробеги глазами заранее — сэкономишь себе вечер мучений.

  • Поставил http:// вместо https://. Telegram молча проигнорирует webhook без шифрования. Адрес обязан начинаться с https://. Если используешь ngrok — бери именно https-ссылку, которую он выдал.
  • Не открыт нужный порт. Telegram стучится только на 443, 80, 88 или 8443. Если бот слушает, например, 5000 — обновления не придут. Либо ставь разрешённый порт, либо пробрасывай через nginx.
  • Одновременно живут polling и webhook. Запустил start_polling, а webhook не снял — будет хаос: обновления то приходят, то нет. Всегда вызывай delete_webhook() перед polling и помни, что одновременно можно только что-то одно.
  • Забыл вызвать set_webhook после смены адреса. Перезапустил ngrok — адрес сменился, а Telegram всё ещё шлёт на старый. Бот «оглох». Решение: вызывать set_webhook на каждом старте (мы для этого и повесили on_startup).
  • Тестируешь на ноуте без публичного адреса. Просто запустить сервер на localhost и ждать webhook бесполезно — Telegram до localhost не дойдёт, это адрес только твоего компьютера. Нужен либо ngrok, либо реальный сервер.

Мини-практика: переключатель режимов

Сделай так, чтобы один и тот же bot.py умел запускаться и в режиме polling (для разработки), и в режиме webhook (для сервера). Подсказки:

  1. Заведи переменную окружения MODE со значением polling или webhook.
  2. Прочитай её через os.getenv("MODE", "polling") — по умолчанию пусть будет polling, чтобы локально ничего не сломать.
  3. Если MODE == "webhook" — поднимай aiohttp-сервер и вызывай set_webhook. Иначе — сначала delete_webhook(drop_pending_updates=True), потом привычный dp.start_polling(bot).
  4. Проверь оба режима: локально через polling, а через ngrok — webhook. Убедись, что хэндлер /start ты не трогал вообще.

Если справишься — у тебя в руках профессиональный приём: один код, два режима, и переключение одной переменной окружения. Так делают в настоящих проектах.

Итоги и что дальше

Сегодня «Цыплёнок-помощник» научился жить на сервере и принимать сообщения по-взрослому. Запомни главное:

  • Polling — бот сам опрашивает Telegram, хорош для разработки на ноуте, не требует адреса.
  • Webhook — Telegram сам присылает обновления на твой сервер; нужен для боевого режима 24/7.
  • Для webhook обязательны публичный адрес, HTTPS с сертификатом и разрешённый порт (443, 80, 88 или 8443).
  • Включается одной командой bot.set_webhook(URL), а ловит обновления маленький aiohttp-сервер через SimpleRequestHandler и setup_application.
  • Хэндлеры при переходе не меняются — меняется только «обвязка» вокруг Dispatcher.
  • Потрогать webhook без покупки сервера помогает ngrok.

У тебя уже есть рабочий webhook-сервер, но он крутится либо на ноуте, либо во временном туннеле ngrok. В следующем уроке мы возьмём настоящий VPS, зальём на него bot.py, настроим, чтобы бот перезапускался сам после сбоя, и наконец отпустим «Цыплёнка-помощника» в свободное плавание — без твоего ноутбука. Поехали выводить бота в реальный мир!

Проверьте себя
1. В чём главное отличие webhook от polling?
AПри webhook бот сам постоянно спрашивает Telegram о новых сообщениях
BПри webhook Telegram сам присылает обновления на адрес твоего сервера
CWebhook работает только без интернета
DWebhook нужен только для отправки фото
2. Что из перечисленного обязательно для работы webhook?
AПубличный адрес и HTTPS
BПлатная подписка Telegram Premium
CЗапущенный ngrok в любом случае
DОтключённый интернет на сервере
3. Какой метод объекта bot говорит Telegram, куда присылать обновления?
Abot.start_polling()
Bbot.send_message()
Cbot.set_webhook(URL)
Dbot.run_app()
4. Зачем перед запуском бота через polling полезно вызвать delete_webhook()?
AЧтобы удалить токен бота
BПотому что одновременно нельзя использовать и polling, и webhook
CЧтобы бот стал быстрее отправлять фото
DЭто вообще не нужно делать
5. На какие порты Telegram готов присылать webhook-обновления?
AТолько на 3000
BНа любой порт без ограничений
CНа 443, 80, 88 или 8443
DТолько на 22
6. Зачем во время разработки удобно использовать ngrok?
AОн бесплатно даёт временный публичный HTTPS-адрес для твоего ноутбука
BОн ускоряет работу хэндлеров в десять раз
CОн заменяет токен от BotFather
DОн нужен, чтобы бот работал без интернета