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 стучится в дверь твоего сервера ровно тогда, когда есть что принести. Никакой беготни вхолостую — обновления прилетают мгновенно и только по делу.
| Параметр | Polling | Webhook |
| Кто проявляет инициативу | Бот сам спрашивает Telegram | Telegram сам присылает боту |
| Нужен публичный адрес | Нет | Да, обязательно |
| Нужен 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 (для сервера). Подсказки:
- Заведи переменную окружения
MODEсо значениемpollingилиwebhook. - Прочитай её через
os.getenv("MODE", "polling")— по умолчанию пусть будет polling, чтобы локально ничего не сломать. - Если
MODE == "webhook"— поднимайaiohttp-сервер и вызывайset_webhook. Иначе — сначалаdelete_webhook(drop_pending_updates=True), потом привычныйdp.start_polling(bot). - Проверь оба режима: локально через 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, настроим, чтобы бот перезапускался сам после сбоя, и наконец отпустим «Цыплёнка-помощника» в свободное плавание — без твоего ноутбука. Поехали выводить бота в реальный мир!