Привет, Хабр!

Если вас заинтересовал заголовок, то вы, скорее всего, уже знакомы с разработкой Telegram- или Discord-ботов. И что также вероятно: для получения обновлений вы используете обычный polling. Сегодня же я вам предлагаю ознакомиться с другим способом получения обновлений - через webhook.

Итак, в этой статье мы узнаем:

  1. Что такое webhook, зачем он нужен?

  2. Чем он лучше того же polling и в чем он ему уступает?

  3. Как подключить Webhook в Telegram и Discord (Он концептуально отличается для сервисов, если интересует Webhook в Discord, сразу переходите во вторую половину статьи).

  4. Дополнительно развернем простейшего бота в Telegram с использованием Webhook.

Что такое Telegram Webhook

Начнём издалека и с Telegram: зачем вообще нужен polling или webhook подход? Чтобы ответить на этот вопрос, нужно хотя бы примерно понимать, как работает бот.

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

Отличия Polling от Webhook

Теперь важно понимать, чем эти способы отличаются.

Представьте, что у вас есть какой-то сервер, который стоит где-то в интернете - не важно где. Telegram должен каким-то образом достучаться до него или, наоборот, вы (ваш сервер) должен регулярно спрашивать Telegram, есть ли что-то новое.

Именно так и работают эти два механизма:

  • polling - режим опроса Telegram. Каждое N количество времени, ваш бот спрашивает у Telegram с помощью специального эндпоинта getUpdates, нет ли каких-либо обновлений;

  • webhook - режим, когда Telegram сам доставляет все обновления на специальный, подготовленный заранее домен с эндпоинтом, который вы указали в методе setWebhook.

Отсюда же можно вынести несколько плюсов и минусов в сторону webhook (мы же все-таки его обозреваем).

Плюсы:

  1. Возможность масштабировать сервер - один из главных плюсов вебхуков. Из-за того, что BotAPI принимает getUpdates только от одного инстанса бота, просто технически невозможно горизонтальное масштабирование проекта при использовании polling - Telegram будет циклически возвращать 409. С использованием Webhook эта боль пропадает. Вы фактически можете развернуть неограниченное количество реплик приложения за балансировщиком.

  2. Мгновенное получение обновлений. Как только происходит что-то новое, Telegram собирает это в пакет и отправляет на специальный эндпоинт (далее будет говорить проще - на вебхук), который вы подготовили ранее.

  3. Меньше нагрузка на сервер. То, что вам не нужно постоянно обращаться к API Telegram для получения обновлений заметно облегчает работу всей программы.

Минусы (?):

  1. Необходимость наличия собственного публичного домена и SSL-сертификата на нем (HTTP Telegram просто не примет).

  2. Сервер должен быть постоянно доступен и всегда возвращать 200 OK.

И вот здесь у многих возникает вполне логичный вопрос: а что, если я не хочу или по каким-то причинам не могу настроить VPS, приобрести домен, настраивать Nginx, SSL и т.д.?

И тут все тоже просто: от проблемы с настройкой и необходимостью собственного домена можно избавиться. Например, можно использовать облачные сервисы для развертывания приложений, которые сразу дают публичный HTTPS-домен. Один из таких вариантов - Amvera.

Что это нам дает:

  • Не нужно покупать домен отдельно - Amvera предоставит публичный домен совершенно бесплатно;

  • Не нужно вручную настраивать SSL;

  • Развернуть бота можно буквально за 2-3 минуты, а после регистрации сервис бесплатно предоставляет 111 рублей* на баланс для тестирования;

  • Webhook можно подключить буквально через минуту после развертывания.

Таким образом этот минус webhook с HTTPS на практике получается очень незначительным.

Сравнение Webhook и Polling

Здесь я просто хочу в паре абзацев рассказать, когда достаточно использования Polling, а когда уже следует пересесть на Webhook.

Итак, Polling лучше:

  • Когда бот тестируется локально;

  • Нет публичного сервера;

  • Простая логика;

  • Стабильно небольшая нагрузка на сервер.

Когда же уже необходимо использовать Webhook:

  • Продакшен;

  • Высокая нагрузка (горизонтальное масштабирование - вопрос времени)

  • Нужны минимальные задержки.

Если коротко: Polling - это не что-то плохое, просто он имеет свою зону применения, которая не очень идет для продакшена.

Кратко про ограничения

  1. Нельзя использовать getUpdates, пока включена работа через Webhook.

  2. Если сертификат самоподписанный (self-signed), то нужно загрузить публичный сертификат параметром certificate в setWebhook

  3. Порты ограничены: разрешены только 443, 80, 88, 8443.

Все это описано в официальной документации в блоке Notes: https://core.telegram.org/bots/api#setwebhook

Помимо этого, очень важно отметить про безопасность: в setWebhook можно указать secret_token, и Telegram будет присылать его вам вместе со всем обновлениями в заголовке X-Telegram-Bot-Api-Secret-Token. Это один из способов навсегда избавиться от злоумышленников, пытающихся прислать фейковые данные через вебхук.

А что c Webhook в Discord?

Хотелось бы, чтобы Discord Webhook был ровно тем же, что Telegram Webhook, однако нет, это немного другое.

В Telegram вебхуки используются именно для получения обновлений - его нельзя использовать для отправки сообщений или любой публикации других событий, тогда как в Discord все наоборот: классический webhook используется для отправки сообщений в канал.

Казалось бы: зачем вебхук, если это просто отправка сообщений? Но на практике это очень удобный механизм.

Что такое Discord Webhook

Discord Webhook - это специальный URL от самого Discord, привязанный к конкретному каналу сервера. Любой, у кого есть этот URL, может отправить в канал сообщение через обычный HTTP POST-запрос.

То есть если Telegram Webhook - это про получение обновлений, то Discord Webhook используется для отправки сообщений.

При создании вебхука в Discord (описано здесь) вы создаете специального webhook-пользователя, от лица которого вы будете писать через вебхук в определенный канал, выбранный при создании вебхука.

Чаще всего это используется для:

  • логов действий;

  • аудита;

  • сообщений от CI/CD (на примере GitHub Bot рассказано в инструкции выше);

  • интеграций с внешними сервисами.

Как создать Discord Webhook

Создается он буквально за минуту.

  1. Открываем сервер.

  2. Переходим в настройки нужного канала.

  3. Раздел Integrations -> Webhooks.

  4. Нажимаем Create Webhook.

  5. Настраиваем имя бота, аватарку и канал и копируем полученный URL.

Создаем webhook в discord
Создаем webhook в discord

Webhook URL - СЕКРЕТ. Кто угодно с URL может отправить совершенно любое сообщение в канал.

А как тогда получать обновления в Discord?

Если вы рассматриваете полноценного Discord-бота, то это не про вебхук. Здесь для получения событий используется другой механизм:

  • либо подключение к WebSocket Gateway;

  • либо настройка Interaction Endpoint (для /команд);

  • либо можно использовать любые библиотеки, в которых это все преднастроенно, где требуется только токен вашего бота и не париться.

Практика: создание бота на Telegram Webhook

Развернём простого бота на Webhook. Это самый обычный и простой бот, который в ответ на каждое сообщение будет возвращать его же - Echo bot.

Главная цель этого блока - показать, как легко и быстро можно развернуть бота и подключить Webhook, потратив минимальное количество времени, нервов и денег.

Подготовка кода

Прежде всего, понятное дело, нужно подготовить сурсы. В состав всего приложения входит всего 3 файла:

  • bot.py - файл, содержащий весь код;

  • requirements.txt - файл, содержащий названия библиотек, необходимых для запуска проекта;

  • amvera.yml - файл, содержащий инструкции для запуска и сборки приложения. Сгенерируется автоматически.

Вот содержимое двух файлов:
bot.py:

import os
from aiogram import Bot, Dispatcher, F
from aiogram.types import Message
from aiogram.webhook.aiohttp_server import SimpleRequestHandler
from aiohttp import web

TOKEN = os.environ["BOT_TOKEN"]
WEBHOOK_URL = os.environ["WEBHOOK_URL"]

WEBHOOK_PATH = "/webhook"
PORT = int(os.environ.get("PORT", 8080))

bot = Bot(TOKEN)
dp = Dispatcher()

@dp.message(F.text)
async def echo(message: Message):
    await message.answer(message.text)

async def on_startup():
    await bot.set_webhook(
        url=WEBHOOK_URL + WEBHOOK_PATH,
        drop_pending_updates=True # Убьет все старые и неактуальные обновления
    )

async def on_shutdown():
    await bot.delete_webhook()

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

app.on_startup.append(lambda _: on_startup())
app.on_shutdown.append(lambda _: on_shutdown())

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

Здесь мы принимаем 3 переменные окружения:

  1. BOT_TOKEN - токен бота;

  2. WEBHOOK_URL - публичный HTTPS-домен, который мы скоро создадим совершенно бесплатно;

  3. PORT - порт, на котором будет запущено веб-приложение.

Самое интересное: установка Webhook при запуске.

async def on_startup():
    await bot.set_webhook(
        url=WEBHOOK_URL + WEBHOOK_PATH,
        drop_pending_updates=True # Убьет все старые и неактуальные обновления
    )

app.on_startup.append(lambda _: on_startup())

Здесь при старте приложения вызывается setWebhook с url = полный адрес до эндпоинта и с drop_pending_updates=True.

HTTP-сервер, где aiohttp создает обычное веб-приложение, а SimpleRequestHandler связывает HTTP-запросы с Dispatcher от aiogram:

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

requirements.txt:

aiogram>=3.0,<4.0
aiohttp

Создание проекта в Amvera

Теперь, когда у нас есть весь необходимый код, мы можем создать проект. Предварительно регистрируемся по ссылке, создаем проект на главной странице проектов с любым названием и тарифом, загружаем bot.py и requirements.txt.

На этапе создания вы можете сразу указать переменные окружения. Я же рекомендую создать переменные уже после создания домена.

На этапе "Конфигурация" у вас будет автоматически сгенерированная конфигурация, поэтому следует обратить внимание на настройки. В параметре scriptName действительно должен быть путь до файла, который отвечает за запуск приложения, а в параметре requirementsPath - путь до requirements.txt.

Задаем конфигурацию
Задаем конфигурацию

Жмём кнопку "Завершить" и сразу открываем проект из списка приложений.

Давайте убедимся, что сервис сгенерировал нам amvera.yml - ту самую конфигурацию, которая настраивалась при создании проекта. Для этого открываем вкладку "Репозиторий" и проверяем наличие файла в Code.

Загруженные в репозиторий файлы
Загруженные в репозиторий файлы

Если все на месте, то мы уже можем создавать домен. Для этого переходим в одноименную вкладку и жмём кнопку в правом верху экрана - "Создать доменное имя".

В настройках выбираем:

  1. Тип подключения: HTTPS,

  2. Тип домена: Бесплатный домен Амвера.

Нажимаем "Применить" - и все, домен готов! Копируем его и переходим во вкладку "Переменные" для последнего этапа настройки проекта.

Здесь нам нужно просто создать необходимые переменные (в том числе WEBHOOK_URL, в значение которой добавить скопированный домен) для этапа запуска.

Задаем переменные и секреты нашего бота на вэбхуках
Задаем переменные и секреты нашего бота на вэбхуках

Вся настройка выполнена, осталось только собрать проект и ожидать его запуска. Для этого во вкладке "Конфигурация" есть кнопка "Собрать".

Проверка работы бота

Если вы все сделали корректно, статус приложения сменитс�� на "Приложение работает", а бот будет отвечать теми же сообщениями, что вы отправили ему:

Проверяем работу webhook бота
Проверяем работу webhook бота

Итог

Итак, мы вместе разобрали:

  1. Что такое Telegram Webhook и чем он лучше Polling;

  2. Что такое Discord Webhook и как с ним работать;

  3. Создали бота на Telegram Webhook.

Я надеюсь, что статья была для вас полезна и вы узнали что-то новое!