Вебхуки
С простым ботом наконец-то разобрались, теперь будем осваивать различные “плюшки”. Первая из них и, пожалуй, самая главная, — вебхуки.
А в чём, собственно, разница?
(Следующий абзац был написан в 2015-2016г., библиотеки с того момента много обновлялись, поэтому в некоторых случаях использовать Long Polling будет не хуже, чем вебхуки).
Давайте для начала разберемся, как боты принимают сообщения. Первый и наиболее простой вариант заключается в периодическом опросе серверов Telegram на предмет наличия новой информации. Всё это осуществляется через т.н. Long Polling, т.е. открывается соединение на непродолжительное время и все обновления тут же прилетают боту. Просто, но не очень надежно. Во-первых, серверы Telegram периодически начинают возвращать ошибку 504 (Gateway Timeout), из-за чего некоторые боты впадают в ступор. Даже pyTelegramBotAPI, используемый мной, не всегда может пережить такое. Во-вторых, если одновременно запущено несколько ботов, вероятность столкнуться с ошибками возрастает. Это вдвойне обидно, если сами боты используются не очень часто.
Вебхуки работают несколько иначе. Устанавливая вебхук, вы как бы говорите серверам Telegram: “Слышь, если кто мне напишет, стукни сюда — (ссылка)”. Отпадает необходимость периодически самому опрашивать серверы, тем самым, исчезает неприятная причина падений ботов. Однако за это приходится платить необходимостью установки полноценного веб-сервера на ту машину, на которой планируется запускать ботов. Что ещё неприятно, надо иметь собственный SSL-сертификат, т.к. вебхуки в телеграме работают только по HTTPS. К счастью, в один прекрасный день появилась поддержка самоподписанных сертификатов. Вот об их применении я и расскажу.
Создаем сертификат
Повторюсь: я не считаю себя супер-мега-крутым специалистом в айти, возможно, я что-то делаю неправильно, тем не менее, это работает и выглядит вполне прилично. Ладно, приступим.
Для начала, установим пакет openssl (для Linux):
sudo apt-get install openssl
Затем сгенерируем приватный ключ:
openssl genrsa -out webhook_pkey.pem 2048
Теперь, внимание, генерируем самоподписанный сертификат вот этой вот длинной командой:
openssl req -new -x509 -days 3650 -key webhook_pkey.pem -out webhook_cert.pem
Нам предложат ввести некоторую информацию о себе: двухбуквенный код страны, имя организации и т.д. Если не хотите ничего вводить, ставьте точку. НО! ВАЖНО! Когда дойдете до предложения ввести Common Name, следует написать IP адрес сервера, на котором будет запущен бот.
Генерация сертификата
В результате получим файлы webhook_cert.pem и webhook_pkey.pem , положим их в какой-нибудь пустой каталог, в котором потом будем создавать бота. Сертификаты готовы, теперь займемся ботом. Чтобы не сильно загружать себе мозги, напишем простого echo-bot’а из урока №1, только теперь с использованием сертификата.
Наш вишнёвый сервер
Выше я упомянул необходимость наличия веб-сервера, для работы с вебхуками. Те, кто умело владеет Apache или Nginx, можете дальше не читать. Лично я никак не мог (и не могу до сих пор) понять, как обрабатывать входящие сообщения от этих серверов в Python. Поэтому, было принято простое и довольно эффективное решение — используем веб-фреймворк CherryPy. Это не самый простой фреймворк по сравнению, например, с Flask, но мы будем использовать именно его.
Итак, установим CherryPy простой командой python3 -m pip install cherrypy
Новый старый бот
Перейдем в каталог с нашими сертификатами и создадим файлы bot.py и config.py . В последнем создадим переменную token , в которую передадим токен нашего бота. Открываем bot.py .
Импортируем 2 библиотеки, зададим необходимые константы и создадим экземпляр бота:
import telebot import cherrypy import config WEBHOOK_HOST = 'IP-адрес сервера, на котором запущен бот' WEBHOOK_PORT = 443 # 443, 80, 88 или 8443 (порт должен быть открыт!) WEBHOOK_LISTEN = '0.0.0.0' # На некоторых серверах придется указывать такой же IP, что и выше WEBHOOK_SSL_CERT = './webhook_cert.pem' # Путь к сертификату WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Путь к приватному ключу WEBHOOK_URL_BASE = "https://%s:%s" % (WEBHOOK_HOST, WEBHOOK_PORT) WEBHOOK_URL_PATH = "/%s/" % (config.token) bot = telebot.TeleBot(config.token)
Обратите внимание, что Telegram поддерживает всего 4 различных порта при работе с самоподписанными сертификатами. Теоретически, это означает, что на одной машине может быть запущено не больше 4 ботов на вебхуках. Практически, это поправимо, но об этом — в следующий раз.
Создадим класс, реализующий экземпляр веб-сервера. Это, в принципе, стандартный код, который от бота к боту сильно меняться не будет:
# Наш вебхук-сервер class WebhookServer(object): @cherrypy.expose def index(self): if 'content-length' in cherrypy.request.headers and \ 'content-type' in cherrypy.request.headers and \ cherrypy.request.headers['content-type'] == 'application/json': length = int(cherrypy.request.headers['content-length']) json_string = cherrypy.request.body.read(length).decode("utf-8") update = telebot.types.Update.de_json(json_string) # Эта функция обеспечивает проверку входящего сообщения bot.process_new_updates([update]) return '' else: raise cherrypy.HTTPError(403)
Посмотрите на название функции: index . Это, по сути, обозначает последнюю часть URL. Поясню на примере: если бы мы хотели получать обновления на адрес 80.100.95.20/webhooksbot , то функцию выше мы бы назвали webhooksbot . index — это аналог отсутствия какой-либо дополнительной маршрутизации. Зачем менять это значение на другое, рассказано здесь 12, сейчас это не нужно.
Итак, что мы видим в коде выше? Принимаем входящие запросы по URL наш.ip.адрес/ , получаем содержимое и прогоняем через набор хэндлеров. Кстати, о них. Т.к. мы реализуем простейших echo-бот, хэндлер нам нужен всего один:
# Хэндлер на все текстовые сообщения @bot.message_handler(func=lambda message: True, content_types=['text']) def echo_message(message): bot.reply_to(message, message.text)
Внимательный читатель всё же заметит одно отличие, о котором я говорить не буду 😉 Заодно ещё один повод открыть документацию.
Далее, отправим серверу наш самоподписанный сертификат и “обратный адрес”, по которому просим сообщать обо всех новых сообщениях:
# Снимаем вебхук перед повторной установкой (избавляет от некоторых проблем) bot.remove_webhook() # Ставим заново вебхук bot.set_webhook(url=WEBHOOK_URL_BASE + WEBHOOK_URL_PATH, certificate=open(WEBHOOK_SSL_CERT, 'r'))
Наконец, укажем настройки нашего сервера и запустим его!
# Указываем настройки сервера CherryPy cherrypy.config.update(< 'server.socket_host': WEBHOOK_LISTEN, 'server.socket_port': WEBHOOK_PORT, 'server.ssl_module': 'builtin', 'server.ssl_certificate': WEBHOOK_SSL_CERT, 'server.ssl_private_key': WEBHOOK_SSL_PRIV >) # Собственно, запуск! cherrypy.quickstart(WebhookServer(), WEBHOOK_URL_PATH, '/': <>>)
Обратите внимание на последнюю строку. Наш сервер в качестве “корня” будет прослушивать адрес вида “ip-адрес/токен_бота”, относительно которого index — это и есть этот адрес. Может, немного криво пояснил, но позднее вам всё станет предельно ясно, сейчас не нужно загромождать голову лишней информацией.
Запустим бота и напишем ему парочку сообщений. Затем посмотрим в окно терминала:
Лог сервера
Если код статуса равен 200 (OK), значит, всё в порядке и бот получил сообщения от сервера.
Подключения Telegram-бота через webhook
Использование Webhook с модулем python-telegram-bot
Все примеры кода по пакету python-telegram-bot запускают Telegram бот с помощью Updater.start_polling() . Он использует метод Telegram API getUpdates для получения новых сообщений для бота. Это вполне нормально для небольших ботов и тестирования, но если бот популярен и получает/отправляет много трафика, то такой подход может замедлить время отклика бота.
Опрос Telegram сервера через webhook — это полезная технология для автоматизации процесса общения с пользователями. Как правило, этот функционал используется для экономии ресурсов на отправку/получение обновлений как собственного сервера, так и серверов Telegram.
Различие между polling и webhook является:
- Опрос polling (через метод .get_updates ) периодически подключается к серверам Telegram для проверки новых обновлений или отправки обработанных сообщений.
- Webhook — это URL-адрес, который передается API Telegram. Каждый раз, когда приходит новое обновление для бота, сервер Telegram отправляет это обновление на указанный URL. Аналогично происходит отправка сообщений.
Содержание:
- Что необходимо для подключения к Telegram через webhook ;
- Встроенный в библиотеку HTTP-сервер для webhook ;
- Обратный прокси-сервер + встроенный сервер webhook ;
- Использование webhook на Heroku;
- Использование nginx с одним доменом/портом для всех ботов;
- Использование haproxy с одним поддоменом на бота;
- Индивидуальное решение, построенное на потоках.
Что необходимо для подключения к Telegram через webhook .
- Публичный IP-адрес или домен. Обычно это означает, что запуск бота должен осуществляться на VPS сервере.
- Необходим SSL-сертификат. Вся связь с серверами Telegram должна быть зашифрована с помощью HTTPS с использованием SSL. В случае подключения polling , о шифровании трафика заботятся серверы Telegram, но если отправка/получение сообщений идет через Webhook, то шифровании должен заботиться клиент/бот. Есть два способа сделать это:
- Подключить проверенный сертификат, выданный доверенным центром сертификации (CA)
- Самостоятельно создать самоподписанный сертификат SSL. Это проще, и в этом нет никакого недостатка.
Чтобы создать самоподписанный SSL-сертификат с помощью openssl , выполните следующую команду в терминале:
$ openssl req -newkey rsa:2048 -sha256 -nodes -keyout private.key -x509 -days 3650 -out cert.pem
Утилита openssl запросит несколько подробностей. Необходимо убедится, что вы ввели правильное полное доменное имя или IP-адрес! Если у сервера есть домен, то введите полное доменное имя (например, sub.example.com ). Если сервер имеет только IP-адрес, то вместо домена введите его IP-адрес. Если введено неверное полное доменное имя или IP-адрес, то бот не получит никаких обновлений от Telegram, при этом не будет никаких ошибок!
Встроенный HTTP-сервер для webhook .
Библиотека python-telegram-bot поставляет встроенный HTTP-сервер, основанный на http.server.HTTPServer . Реализация HTTPServer , которая плотно интегрирована в модуль расширения telegram.ext и может быть запущен с помощью updater.start_webhook / application.run_webhook . Этот веб-сервер также занимается расшифровкой HTTPS-трафика. Это самый простой способ настроить webhook.
Однако у этого решения есть ограничение. Telegram в настоящее время поддерживает только четыре порта для веб-перехватчиков: 443, 80, 88 и 8443. В результате можно запускать не более четырех ботов на одном домене/IP-адресе.
Если это не проблема, то можно использовать код ниже или аналогичный, чтобы запустить бот с webhook. Адрес прослушивания должен быть либо ‘0.0.0.0’ , либо, если нет разрешения на это, общедоступный IP-адрес сервера. Порт может быть одним из 443, 80, 88 или 8443. Рекомендуется установить секретный токен в параметре secret_token , чтобы никто не мог отправить боту поддельные обновления. Аргументы key и cert должны содержать путь к файлам, которые создали ранее. Аргумент webhook_url должен быть фактическим URL-адресом webhook . При этом необходимо перед URL-адресом webhook использовать протокол https:// , домен или IP-адрес, которые установлен в качестве полного доменного имени сертификата, а также правильный порт и URL-адрес.
application.run_webhook( listen='0.0.0.0', port=8443, secret_token='ASecretTokenIHaveChangedByNow', key='private.key', cert='cert.pem', webhook_url='https://example.com:8443' ) # или updater.start_webhook( listen='0.0.0.0', port=8443, secret_token='ASecretTokenIHaveChangedByNow', key='private.key', cert='cert.pem', webhook_url='https://example.com:8443' )
Обратный прокси-сервер + встроенный сервер webhook .
Чтобы решить эту проблему, можно использовать обратный прокси-сервер, такой как nginx или haproxy , а также можно использовать Heroku .
В этой модели обратный прокси ( nginx ), слушает публичный IP-адрес, принимает все запросы webhook и пересылает их на правильный экземпляр локально запущенных встроенных в python-telegram-bot серверов webhook. Обратный прокси также выполняет завершение SSL, то есть расшифровывает HTTPS-соединение, поэтому серверы webhook получают уже расшифрованный трафик. Эти серверы могут работать на любом порту, а не только на четырех разрешенных Telegram портах, т.к. сервера Telegram напрямую подключается только к обратному прокси-серверу.
В зависимости от того, какой прокси-сервер используется, реализация будет выглядеть немного иначе. Ниже перечислены несколько возможных настроек.
Использование webhook на Heroku.
На Heroku использовать webhook можно на свободном плане, т.к. он будет автоматически управлять временем простоя. Для пользователя Heroku будет настроен обратный прокси и создана среда исполнения. Из этой среды необходимо будет извлечь порт, который бот должен прослушивать. Heroku управляет SSL на стороне прокси-сервера, следовательно не нужно создавать сертификат самостоятельно.
import os TOKEN = "TOKEN" PORT = int(os.environ.get('PORT', '8443')) # добавим обработчики application.run_webhook( listen="0.0.0.0", port=PORT, secret_token='ASecretTokenIHaveChangedByNow', webhook_url="https://.herokuapp.com/" )
Использование nginx с одним доменом/портом для всех ботов
Все боты устанавливают свой URL-адрес на один и тот же домен и порт, но с другим url_path . Встроенный в python-telegram-bot сервер обычно запускается по адресу localhost или 127.0.0.1, порт может быть любым.
Примечание: если нет домена, связанного с сервером, то example.com может быть заменен IP-адресом.
Пример кода для запуска бота:
application.run_webhook( listen='127.0.0.1', port=5000, url_path='1', secret_token='ASecretTokenIHaveChangedByNow', webhook_url='https://example.com/1', cert='cert.pem' )
Пример конфигурации для nginx с двумя настроенными ботами (представлены важные части конфига):
server < listen 443 ssl; server_name example.com; ssl_certificate cert.pem; ssl_certificate_key private.key; location /TOKEN1 < proxy_pass http://127.0.0.1:5000/1/; >location /TOKEN2 < proxy_pass http://127.0.0.1:5001/2/; >>
Использование haproxy с одним поддоменом на бота.
При таком подходе, каждому боту присваивается свой собственный поддомен. Если сервер имеет домен example.com , то можно создать поддомены например: bot1.example.com , bot2.example.com и т. д. Понадобится один сертификат для каждого бота с полным доменным именем, установленным для соответствующего поддомена. Встроенный в python-telegram-bot сервер обычно запускается по адресу localhost или 127.0.0.1, порт может быть любым.
Примечание: Необходимо иметь домен привязанный к IP-адресу сервера.
Пример кода для запуска бота:
application.run_webhook( listen='127.0.0.1', port=5000, secret_token='ASecretTokenIHaveChangedByNow', webhook_url='https://bot1.example.com', cert='cert_bot1.pem') )
Пример конфигурации для haproxy с двумя настроенными ботами (сведен к важным частям конфига) . Опять же: полное доменное имя обоих сертификатов должно соответствовать значению в ssl_fc_sni . Кроме того, файлы .pem представляют собой объединенные файлы private.key и cert.pem .
frontend public-https bind 0.0.0.0:443 ssl crt cert_key_bot1.pem crt cert_key_bot2.pem option httpclose use_backend bot1 if < ssl_fc_sni bot1.example.com >use_backend bot2 if < ssl_fc_sni bot2.example.com >backend bot1 mode http option redispatch server bot1.example.com 127.0.0.1:5000 check inter 1000 backend bot2 mode http option redispatch server bot2.example.com 127.0.0.1:5001 check inter 1000
Индивидуальное решение, построенное на потоках.
Не обязательно использовать встроенный веб-сервер. Если решите пойти этим путем, то не следует использовать класс Updater . Модуль telegram.ext был переработан с учетом этой опции, поэтому все равно можно использовать класс Application , чтобы извлечь выгоду из фильтрации / сортировки сообщений, которые он предоставляет. НО придется проделать некоторую работу вручную.
from telegram import Bot from telegram.ext import Application application = Application.builder().token('TOKEN').build() # Регистрация обработчиков здесь # Получаем `update_queue`, из которого приложение получает обновления для обработки. update_queue = application.update_queue start_fetching_updates(update_queue) # Запускаем приложение async with application: application.start() # и останавливаем, когда срабатывает # какой-либо механизм отключения: application.stop()
Здесь start_fetching_updates — это заполнитель для любого метода, который используется для настройки веб-перехватчика. Важной частью является то, что полученные обновления в update_queue ставятся в очередь. То есть вызывается await update_queue.put(update) , где update — это декодированный объект Update (используйте Update.de_json(json.loads(text), bot ) для декодирования обновления из полученных данных JSON).
Альтернатива: нет длительных задач.
Если BOT не использует длительные задачи, запущенные с помощью application.start() , то это не нужно! Вместо того, чтобы помещать обновления в update_queue , можно напрямую обрабатывать их через application.process_update(update) .
Простой пример пользовательского вебхука.
Простой пример бота, который использует пользовательскую настройку веб-перехватчика и обрабатывает пользовательские обновления.Для пользовательской настройки вебхука используются библиотеки starlette и uvicorn . Эти модули необходимо установить как pip install starlette=0.20.0 uvicorn=0.17.0 .
Обратите внимание, что можно использовать любой другой фреймворк веб-сервера на основе asyncio для пользовательской настройки веб-перехватчика.
- Установите токен бота, URL-адрес, admin chat_id и порт в начале основной функции.
- Также может понадобиться изменить значение listen в конфигурации uvicorn , чтобы оно соответствовало вашей настройке.
- Нажатие Ctrl-C в командной строке остановит бота.
import asyncio import html import logging from dataclasses import dataclass from http import HTTPStatus import uvicorn from starlette.applications import Starlette from starlette.requests import Request from starlette.responses import PlainTextResponse, Response from starlette.routing import Route from telegram import __version__ as TG_VER try: from telegram import __version_info__ except ImportError: __version_info__ = (0, 0, 0, 0, 0) # type: ignore[assignment] if __version_info__ (20, 0, 0, "alpha", 1): raise RuntimeError( f"Пример несовместим с текущей версией PTB TG_VER>. " f"Чтобы просмотреть TG_VER> версия этого примера, " f"посетите https://docs.python-telegram-bot.org/en/vTG_VER>/examples.html" ) from telegram import Update from telegram.constants import ParseMode from telegram.ext import ( Application, CallbackContext, CommandHandler, ContextTypes, ExtBot, TypeHandler, ) # Enable logging logging.basicConfig( format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO ) logger = logging.getLogger(__name__) @dataclass class WebhookUpdate: """Класс данных для переноса пользовательского типа обновления""" user_id: int payload: str class CustomContext(CallbackContext[ExtBot, dict, dict, dict]): """ Пользовательский класс CallbackContext, который делает user_data доступным для обновлений типа `WebhookUpdate`. """ @classmethod def from_update( cls, update: object, application: "Application", ) -> "CustomContext": if isinstance(update, WebhookUpdate): return cls(application=application, user_id=update.user_id) return super().from_update(update, application) async def start(update: Update, context: CustomContext) -> None: """Показать сообщение с инструкциями по использованию этого бота.""" url = context.bot_data["url"] payload_url = html.escape(f"url>/submitpayload?user_id=&payload=") text = ( f"Чтобы проверить, все ли еще запущен бот, вызовите url>/healthcheck.\n\n" f"Чтобы опубликовать пользовательское обновление, вызовите payload_url> ." ) await update.message.reply_html(text=text) async def webhook_update(update: WebhookUpdate, context: CustomContext) -> None: """Обратный вызов, который обрабатывает пользовательские обновления.""" chat_member = await context.bot.get_chat_member(chat_id=update.user_id, user_id=update.user_id) payloads = context.user_data.setdefault("payloads", []) payloads.append(update.payload) combined_payloads = "\n• ".join(payloads) text = ( f"Пользователь chat_member.user.mention_html()> отправил новую полезную нагрузку. " f"На данный момент они отправили следующее: \n\n• combined_payloads> " ) await context.bot.send_message( chat_id=context.bot_data["admin_chat_id"], text=text, parse_mode=ParseMode.HTML ) async def main() -> None: """Настройки приложения и пользовательского веб-сервера.""" url = "https://domain.tld" admin_chat_id = 123456 port = 8000 context_types = ContextTypes(context=CustomContext) # Здесь устанавливаем для `updater` значение `None`, потому что нужно, чтобы пользовательский # сервер обрабатывал обновления, и, следовательно, не нужен экземпляр `Updater`. application = ( Application.builder().token("TOKEN").updater(None).context_types(context_types).build() ) # сохраним значения в `bot_data` таким образом, чтобы можно было легко получить # к ним доступ при обратных вызовах application.bot_data["url"] = url application.bot_data["admin_chat_id"] = admin_chat_id # регистрируем обработчики application.add_handler(CommandHandler("start", start)) application.add_handler(TypeHandler(type=WebhookUpdate, callback=webhook_update)) # Передать настройки вебхука в телеграмм await application.bot.set_webhook(url=f"url>/telegram") # Поднимаем веб-сервер async def telegram(request: Request) -> Response: """Обрабатываем входящие обновления Telegram, помещая их в `update_queue`""" await application.update_queue.put( Update.de_json(data=await request.json(), bot=application.bot) ) return Response() async def custom_updates(request: Request) -> PlainTextResponse: """ Обрабатываем входящие обновления веб-перехватчика, также помещая их в очередь `update_queue`, если необходимые параметры были переданы правильно. """ try: user_id = int(request.query_params["user_id"]) payload = request.query_params["payload"] except KeyError: return PlainTextResponse( status_code=HTTPStatus.BAD_REQUEST, content="Передайте как `user_id`, так и полезную " "нагрузку в качестве параметров запроса.", ) except ValueError: return PlainTextResponse( status_code=HTTPStatus.BAD_REQUEST, content="`user_id` должен быть строкой!", ) await application.update_queue.put(WebhookUpdate(user_id=user_id, payload=payload)) return PlainTextResponse("Спасибо за отправку! Оно пересылается.") async def health(_: Request) -> PlainTextResponse: """Для конечной точки работоспособности отвечаем текстовым сообщением.""" return PlainTextResponse(content="Бот по-прежнему работает нормально :)") starlette_app = Starlette( routes=[ Route("/telegram", telegram, methods=["POST"]), Route("/healthcheck", health, methods=["GET"]), Route("/submitpayload", custom_updates, methods=["POST", "GET"]), ] ) webserver = uvicorn.Server( config=uvicorn.Config( app=starlette_app, port=port, use_colors=False, host="127.0.0.1", ) ) # Запуск приложения и веб-сервера вместе async with application: await application.start() await webserver.serve() await application.stop() if __name__ == "__main__": asyncio.run(main())
- КРАТКИЙ ОБЗОР МАТЕРИАЛА.
- Переход на асинхронный python-telegram-bot версии 20.x
- Чистый интерфейс Python для Telegram Bot API
- Команды и оповещения @BotFather в Telegram
- Обработка сообщений модулем python-telegram-bot
- Фильтры сообщений модуля python-telegram-bot
- Хранение временных данных модулем python-telegram-bot
- Настройки по умолчанию модуля python-telegram-bot
- Планировщик сообщений модуля python-telegram-bot
- Форматирование и отправка сообщений в python-telegram-bot
- Работа с файлами/media, модуль python-telegram-bot
- Меню из кнопок, модуль python-telegram-bot
- Объект CallbackContext модуля python-telegram-bot
- Подключения Telegram-бота через webhook
- Обработка исключений модуля python-telegram-bot
- Создание Inline-бота, модуль python-telegram-bot
- Работа с опросами в модуле python-telegram-bot
- Создание разговоров ConversationHandler в python-telegram-bot
- Перезапуск телеграмм-бота в случае ошибки
- Декоратор-обработчик сообщений в python-telegram-bot
- Авторизация на сайте через Telegram Passport
- Ведение публикаций в Telegram-канале с python-telegram-bot
- UTF коды emoji/эмодзи для отправки в Telegram из Python
Telegram бот через webhook
Я тогда давно написал статью про создание Telegram бота, и обещал дополнить её описанием настройки работы через webhook, но так и не дополнил. Вот только сейчас дошли руки.

Что это такое
Как пишут в документации, общаться с серверами Telegram бот может двумя способами:
- getUpdates - pull: ваш бот постоянно дёргает сервер Telegram и проверяет есть ли новые сообщения;
- setWebhook - push: по мере поступления новых сообщений сервер Telegram отправляет их вашему боту.
Разницу можно изобразить следующим образом:

Очевидно, что второй способ ( setWebhook ) рациональнее для всех участников процесса. Однако в нём присутствует неявная сложность: кто-то должен принимать сообщения от Telegram на стороне бота, то есть необходим веб-сервер или его эквивалент.
Как настроить
Что нужно сделать:
- Заиметь доменное имя для сервера и получить на него сертификат (например, от Let’s Encrypt). Документация также говорит, что в случае самоподписанного сертификата можно обойтись и просто IP адресом, но этого я не пробовал;
- Запилить серверную часть на стороне бота (куда будет ломиться Telegram);
- Зарегистрировать адрес серверной части в Telegram (зацепить webhook на endpoint), чтобы Telegram знал, куда ломиться с сообщениями.
Сертификат
С доменом и сертификатом просто. Домен у меня уже был, а сертификат я получил по этой инструкции.
Вариант с самоподписанным сертификатом на прямой IP адрес я оставляю вам на самостоятельное изучение.
Серверная часть
Серверная часть чуть посложнее. Я переделал текущую реализацию бота на pyTelegramBotAPI, используя пример для AIOHTTP.
Ставим необходимые пакеты:
pip install pyTelegramBotAPI pip install aiohttp pip install cchardet pip install aiodns
И сокращённо код бота теперь такой:
import config import telebot from aiohttp import web import ssl WEBHOOK_LISTEN = "0.0.0.0" WEBHOOK_PORT = 8443 WEBHOOK_SSL_CERT = "/etc/letsencrypt/live/YOUR.DOMAIN/fullchain.pem" WEBHOOK_SSL_PRIV = "/etc/letsencrypt/live/YOUR.DOMAIN/privkey.pem" API_TOKEN = config.token bot = telebot.TeleBot(API_TOKEN) app = web.Application() # process only requests with correct bot token async def handle(request): if request.match_info.get("token") == bot.token: request_body_dict = await request.json() update = telebot.types.Update.de_json(request_body_dict) bot.process_new_updates([update]) return web.Response() else: return web.Response(status=403) app.router.add_post("/ /", handle) help_string = [] help_string.append("*Some bot* - just a bot.\n\n") help_string.append("/start - greetings\n") help_string.append("/help - shows this help") # - - - messages @bot.message_handler(commands=["start"]) def send_welcome(message): bot.send_message(message.chat.id, "Ololo, I am a bot") @bot.message_handler(commands=["help"]) def send_help(message): bot.send_message(message.chat.id, "".join(help_string), parse_mode="Markdown") # - - - context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) context.load_cert_chain(WEBHOOK_SSL_CERT, WEBHOOK_SSL_PRIV) # start aiohttp server (our bot) web.run_app( app, host=WEBHOOK_LISTEN, port=WEBHOOK_PORT, ssl_context=context, )
Что здесь происходит: мы запускаем мини-веб-сервер, который слушает порт 8443 и отвечает на запросы через определённый endpoint, который образован токеном бота. Токен используется здесь как достаточно уникальный идентификатор, чтобы какой-нибудь мимокрокодил из интернета не навызывал бота и не натворил дел. Полный адрес endpoint’а будет выглядеть вот так: https://YOUR.DOMAIN:8443/YOUR-TOKEN/ .
Обратите также внимание на отличия от стандартного примера из репозитория:
- в качестве файла сертификата указан fullchain.pem , а не cert.pem ;
- удалён код снятия и установки webhook’а.
Так как бота я запускаю не из-под root’а, сервис начал валиться с такой ошибкой:
python-bot[1824]: Traceback (most recent call last): python-bot[1824]: File "/usr/local/bin/bot/bot.py", line 142, in python-bot[1824]: context.load_cert_chain(WEBHOOK_SSL_CERT, WEBHOOK_SSL_PRIV) python-bot[1824]: PermissionError: [Errno 13] Permission denied systemd[1]: telegram-bot.service: Main process exited, code=exited, status=1/FAILURE systemd[1]: telegram-bot.service: Failed with result 'exit-code'.
То есть, у пользователя, из-под которого выполняется скрипт, нет доступа к /etc/letsencrypt/ , чтобы открыть файл сертификата. Я попытался дать доступ к каталогу для новой группы, включив в неё этого пользователя:
groupadd letsencrypt usermod -a -G letsencrypt userforbot chgrp -R letsencrypt /etc/letsencrypt/
Но он один фиг не мог открыть файлы оттуда, даже простой ls выдавал ошибку доступа. В общем, или мои познания Linux полный отстой, или одно из двух. Пришлось тупо назначить его владельцем:
chown -R userforbot:letsencrypt /etc/letsencrypt/
Тогда сервис запустился нормально.
Регистрация
Теперь осталось самое, как оказалось, сложное - зарегистрировать endpoint бота в Telegram. Сложности возникли потому, что я сначала неправильно понял принцип составления endpoint’а, а также из-за проблем с проверкой сертификата.
Для установки/регистрации webhook’а нужно выполнить следующий HTTP запрос (можно просто открыть этот URL в браузере):
https://api.telegram.org/botYOUR-TOKEN/setWebhook?url=https://YOUR.DOMAIN:8443/YOUR-TOKEN/
Пока я экспериментировал и разбирался с форматом endpoint’а, Telegram возвращал мне нормальный результат:
"description": "Webhook was set", "ok": true, "result": true >
Но потом я его видимо задолбал, и он стал возвращать мне следующее:
"ok": false, "error_code": 504, "description": "Gateway Timeout" >
Но оказалось, что это ни на что не влияет, и webhook нормально устанавливается, так что можно даже не дожидаться таймаута, а просто отменять запрос через пару секунд.
Проверить статус webhook’а можно таким запросом:
https://api.telegram.org/botYOUR-TOKEN/getWebhookInfo
Если всё нормально, должно вернуть такое:
"ok": true, "result": "url": "https://YOUR.DOMAIN:8443/YOUR-TOKEN/", "has_custom_certificate": false, "pending_update_count": 0, "max_connections": 40 > >
Как видим, в поле url стоит наш endpoint.
Однако, мне оно сейчас возвращает такое:
"ok": true, "result": "url": "https://YOUR.DOMAIN:8443/YOUR-TOKEN/", "has_custom_certificate": false, "pending_update_count": 0, "last_error_date": 1543762687, "last_error_message": "SSL error ", "max_connections": 40 > >
Что указывает на некие проблемы с сертификатом. При этом бот работает нормально, то есть эта ошибка ни на что не влияет. Однако, если вместо fullchain.pem оставить cert.pem (как было указано в примере), то бот работать перестанет.
Стоит также отметить, что если вы установили webhook, то опрос Telegram через getUpdates работать больше будет. Чтобы снять webhook, надо отправить тот же самый запрос, что и для установки, но на этот раз без параметра url :
https://api.telegram.org/botYOUR-TOKEN/setWebhook
"ok": true, "result": true, "description": "Webhook was deleted" >
Ну и всё, не так уж и сложно. Если бы в документации (и сторонних манулах из интернетов) была указана такая простая вещь, что для webhook’а всего-то нужен лишь веб-сервер на стороне бота, я бы это сделал уже сто лет назад. Конечно, продвинутым чувакам это скорее всего было очевидно сразу, но мнe - нет.
Note that even if you sign-in with your e-mail or GitHub, you still won't be subscribed to replies. That is by design of remark42, unfortunately. To get updates about new replies/comments you need to explicitly subscribe to them either via e-mail or RSS.
Telegram Webhook
Telegram Webhook – это технология, позволяющая отслеживать в чате события в реальном времени и отправлять информацию о нем на указанный адрес. Если вы создали бот, то метод необходим, чтобы система могла реагировать на сообщения и действия пользователей. Получая информацию о них, сервер отправляет ее программе бота, в алгоритм которой заложена обработка.

Что нужно для рабочего коннекта Webhook
- Адрес (URL) страницы с программным кодом;
- Поддержка IPv4, но рекомендуем поддержка IPv6 из-за частой блокировки телеграмма по IPv4;
- Возможность сервера обрабатывать HTTPS трафик;
- Наличие SSL сертификата.
Команды работы с методом описаны в Telegram Webhook Bot API. К ним относятся:
- setWebhook – установка соединения . При этом необходимо передать адрес, на который будут отправляться сообщения из чата;
- deleteWebhook – удаление соединения . Указание предыдущей команды без параметров также удалит его;
- getWebhookInfo – получение текущего статуса соединения.
Активация Webhook Telegram
Чтобы использовать метод постоянного коннекта сервера и бота, следует запустить программный код, в котором выполняется команда API для Телеграм.
Для официального сертификата SSL :
https://api.telegram.org/botТОКЕН/setWebhook?url=ВАША_ССЫЛКА
Для самоподписанного сертификата :
https://api.telegram.org/botТОКЕН/setWebhook?url=ВАША_ССЫЛКА&certificate=ФАЙЛ_СЕРТИФИКАТА
ВАША_ССЫЛКА — это ссылка на скрипт, обрабатывающий запросы от сервера Telegram, например сообщения или команды для вашего бота.
Какая команда будет использоваться для отправки сообщения в мессенджер зависит от языка программирования.
Например, для PHP это будет curl_exec(), а для Python — requests.get().
Для установки сертификата на сервера чаще всего используется криптографический пакет с открытым исходным кодом OpenSSL . Его можно скачать с официального сайта. Настройка зависит от операционной системы и сервера.

Официальные цифровые подписи устанавливаются на хостинге провайдером. Эта одна из услуг большинства компаний.
Прежде чем использовать Webhooks убедитесь, что вам доступен этот функционал.
Создание бота для Telegram на Webhooks предпочтительнее, чем использования метода getUpdates. Во втором случае приходится закладывать в код постоянное обращение этой команды к боту. Это делается в циклическом режиме. В конце концов начинаются баги, и он подвисает. Webhook же держит постоянное соединение, но запросы отправляются только когда произошло событие, например, пользователь ввел сообщение.