Lede: HTTPS-адрес, проверка запросов, быстрый ответ, очередь обработки и защита от повторной обработки обновлений.
Webhook для Telegram-бота — это HTTPS-endpoint, на который Telegram отправляет обновления при каждом событии: новом сообщении, нажатии кнопки, присоединении участника. Для настройки требуется действующий сертификат HTTPS, регистрация URL через метод setWebhook, проверка подлинности запросов с помощью secret_token, ответ сервера в течение установленного времени и защита от повторной обработки одинаковых update_id. Дополнительно нужна очередь для обработки задач, чтобы избежать таймаутов и не терять обновления при временных проблемах.
Как выбрать webhook или long polling
Telegram Bot API даёт два взаимоисключающих способа получения Update: getUpdates и webhook. Long polling подходит и для продакшена, если процесс бота запущен в одном экземпляре и публичный HTTPS-endpoint не нужен. Webhook удобен, когда уже есть публичный HTTPS-сервис, нужно масштабировать приём или интегрировать его с веб-приложением.
Пока установлен webhook, getUpdates не работает. Для перехода на polling сначала явно вызовите deleteWebhook и осознанно выберите drop_pending_updates. Вызов getUpdates сам webhook не удаляет.
Требования к HTTPS-endpoint
Telegram принимает только HTTPS-адреса с действующим сертификатом. Самоподписанные сертификаты поддерживаются, но их нужно передать в параметре certificate при вызове setWebhook. Для продуктовых систем проще использовать сертификат от Let's Encrypt или другого публичного центра сертификации.
По умолчанию разрешены порты 443, 80, 88 и 8443. Если ваш сервер слушает другой порт, Telegram не сможет доставить обновления. Проверьте конфигурацию веб-сервера и убедитесь, что выбранный порт открыт в файрволе.
URL должен содержать уникальный случайный путь, например https://example.com/telegram/incoming/7f3c-random-path. Не вставляйте токен бота в URL: адрес может попасть в access-логи, метрики и отчёты об ошибках. Для аутентификации запроса используйте отдельный secret_token.
Настройка webhook через setWebhook
Метод setWebhook регистрирует адрес для доставки обновлений. Минимальный вызов выглядит так:
POST https://api.telegram.org/bot<TOKEN>/setWebhook
Content-Type: application/json
{
"url": "https://example.com/telegram/incoming/7f3c-random-path"
}
После успешного ответа Telegram начнёт отправлять POST-запросы на указанный адрес. Каждый запрос содержит JSON-объект Update с полями update_id, message, callback_query и другими, в зависимости от типа события.
Дополнительные параметры метода:
certificate— файл самоподписанного сертификата в формате PEM, если не используете публичный центр сертификации;ip_address— фиксированный IP, если DNS-запись ещё не обновлена или вы хотите ускорить доставку;max_connections— максимальное число одновременных HTTPS-соединений от Telegram (по умолчанию 40, можно задать от 1 до 100);allowed_updates— явный список нужных типов; если параметр не передать, Bot API сохранит прежнюю настройку; пустой список означает все типы, кромеchat_member,message_reactionиmessage_reaction_count;drop_pending_updates— еслиtrue, все накопленные обновления будут удалены при регистрации webhook;secret_token— строка до 256 символов, которую Telegram передаёт в заголовкеX-Telegram-Bot-Api-Secret-Tokenкаждого запроса. Используйте этот параметр для проверки подлинности.
Пример с полным набором параметров:
POST https://api.telegram.org/bot<TOKEN>/setWebhook
Content-Type: application/json
{
"url": "https://example.com/telegram/incoming/7f3c-random-path",
"secret_token": "random-string-256-chars",
"max_connections": 50,
"allowed_updates": ["message", "edited_message", "callback_query", "pre_checkout_query"],
"drop_pending_updates": false
}
После настройки проверьте статус командой getWebhookInfo:
GET https://api.telegram.org/bot<TOKEN>/getWebhookInfo
Ответ содержит поля url, has_custom_certificate, pending_update_count, last_error_date и last_error_message. Если pending_update_count растёт, значит ваш сервер не отвечает вовремя или возвращает ошибку.
Проверка подлинности запросов
Любой, кто знает адрес вашего webhook, может отправить POST-запрос с произвольным JSON. Без проверки подлинности злоумышленник сможет имитировать обновления и заставить бота выполнить нежелательные действия. Параметр secret_token решает эту проблему: Telegram передаёт заданную строку в заголовке X-Telegram-Bot-Api-Secret-Token, а ваш сервер сравнивает её с сохранённым значением.
Алгоритм проверки:
- Сгенерируйте случайную строку длиной 32–64 символа (буквы, цифры, дефисы, подчёркивания).
- Сохраните её в переменной окружения или конфигурационном файле.
- Передайте в
secret_tokenпри вызовеsetWebhook. - В обработчике webhook извлеките заголовок
X-Telegram-Bot-Api-Secret-Token. - Сравните значение с сохранённым токеном. Если не совпадает, верните HTTP 403 и проигнорируйте тело запроса.
Пример на Python (Flask):
import os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET_TOKEN = os.environ.get("TELEGRAM_SECRET_TOKEN")
if not SECRET_TOKEN:
raise RuntimeError("TELEGRAM_SECRET_TOKEN must be non-empty")
@app.route("/telegram/incoming/7f3c-random-path", methods=["POST"])
def webhook():
if request.headers.get("X-Telegram-Bot-Api-Secret-Token") != SECRET_TOKEN:
abort(403)
update = request.get_json()
# Обработка update
return "", 204
Дополнительная мера — проверка источника по IP. Telegram публикует диапазоны адресов в документации, но они могут меняться. Полагаться только на IP небезопасно: используйте secret_token как основной механизм, а фильтрацию по IP — как дополнительный уровень защиты.
Быстрый ответ и асинхронная обработка
Telegram считает доставку успешной при любом ответе 2xx, а не только 200. При другом статусе Telegram повторяет запрос и после разумного числа попыток прекращает их. Поэтому нельзя отвечать 2xx до того, как Update надёжно сохранён в своей базе или очереди.
Правильная архитектура разделяет приём и обработку:
- Endpoint принимает POST-запрос, проверяет
secret_token, извлекаетupdate_idи тело обновления. - В транзакции базы выполняется
INSERT ... ON CONFLICT DO NOTHINGв inbox с уникальнымupdate_id. - Только после commit endpoint возвращает HTTP 204; при ошибке хранилища возвращает 503, чтобы Telegram повторил доставку.
- Воркер забирает обновление из очереди и выполняет бизнес-логику: отправку сообщений, запросы к базе данных, вызовы внешних API.
Упрощённый пример на Flask и PostgreSQL:
import os
from flask import Flask, request, abort
from psycopg import connect
app = Flask(__name__)
SECRET_TOKEN = os.environ.get("TELEGRAM_SECRET_TOKEN")
if not SECRET_TOKEN:
raise RuntimeError("TELEGRAM_SECRET_TOKEN must be non-empty")
@app.post("/telegram/incoming/7f3c-random-path")
def webhook():
if request.headers.get("X-Telegram-Bot-Api-Secret-Token") != SECRET_TOKEN:
abort(403)
update = request.get_json(force=True)
with connect(os.environ["DATABASE_URL"]) as db:
db.execute(
"INSERT INTO telegram_inbox(update_id, body, state) "
"VALUES (%s, %s, 'pending') ON CONFLICT (update_id) DO NOTHING",
(update["update_id"], update),
)
return "", 204
Отдельный диспетчер выбирает pending-строки через FOR UPDATE SKIP LOCKED, пытается поместить их в очередь и меняет статус только после успеха. Если enqueue упал, строка остаётся pending и попадает в следующую попытку. Воркер дополнительно защищает каждую бизнес-операцию её собственным уникальным ключом.
Защита от повторной обработки обновлений
Каждый Update содержит уникальный update_id, который Bot API прямо рекомендует для игнорирования повторов. Если endpoint не вернул статус 2xx, Telegram повторит тот же Update. Без дедупликации бот может дважды выполнить бизнес-действие.
Механизм защиты:
- Извлеките
update_idиз JSON. - Атомарно вставьте
update_idи тело в durable inbox с уникальным ограничением. - Если строка уже есть, верните 204: повтор уже сохранён и не требует второй записи.
- Если commit не состоялся, верните 503; не ставьте отметку
processedдо успешного enqueue и бизнес-действия.
Для inbox нужно durable-хранилище с атомарной вставкой и уникальным ключом. Обычный словарь в памяти теряется при перезапуске. Схема EXISTS, затем SETEX и затем enqueue тоже неверна: первые два шага допускают гонку, а сбой между отметкой и enqueue теряет Update. Если выбран Redis, нужен атомарный Lua-скрипт или Redis Streams с consumer groups, подтверждением после обработки и восстановлением pending-записей.
Обработка ошибок и повторная доставка
Если endpoint вернул статус вне диапазона 2xx или не ответил вовремя, Telegram повторит запрос и после «разумного количества попыток» прекратит доставку. Точное расписание Bot API не обещает, поэтому нельзя закладываться на конкретные интервалы.
Логирование ошибок критически важно. Записывайте в лог каждое обновление, которое не удалось обработать: update_id, тип ошибки, стек вызовов. Так вы сможете понять, какие обновления потеряны, и воспроизвести их вручную.
Проверяйте getWebhookInfo регулярно. Поля last_error_date и last_error_message покажут последнюю проблему. Если pending_update_count растёт, увеличьте производительность воркеров или масштабируйте инфраструктуру.
Для критичных сценариев настройте мониторинг: отправляйте метрику pending_update_count в Prometheus, Datadog или CloudWatch и настройте алерт при превышении порога. Так вы узнаете о проблеме до того, как пользователи начнут жаловаться.
Типичные ошибки при настройке webhook
Несколько ошибок встречаются регулярно:
- Недействительный сертификат HTTPS. Telegram проверяет цепочку сертификатов. Убедитесь, что сертификат не истёк, содержит корректное имя домена и подписан доверенным центром. Проверить можно командой
curl -v https://example.com/webhook/...или онлайн-инструментами вроде SSL Labs. - Неправильный порт. Если ваш сервер слушает порт 8080, а Telegram разрешает только 443, 80, 88 и 8443, обновления не будут доставлены. Настройте прокси (nginx, Caddy, Traefik) для проксирования с разрешённого порта на внутренний.
- Отсутствие проверки secret_token. Без проверки ваш бот уязвим к подделке обновлений. Всегда сравнивайте заголовок
X-Telegram-Bot-Api-Secret-Tokenс сохранённым значением. - Долгая обработка в endpoint. Если вы выполняете тяжёлые операции синхронно, Telegram не дождётся ответа и повторит доставку. Переместите обработку в очередь.
- Повторная обработка обновлений. Без проверки
update_idодно и то же событие будет обработано несколько раз. Сохраняйте идентификаторы в Redis или базе данных. - Одновременное использование getUpdates и webhook. Telegram не разрешает оба метода одновременно. Если вы вызвали
setWebhook, long polling отключится автоматически. - Накопление обновлений после сбоя. Если бот был недоступен несколько часов, накопятся тысячи обновлений. Используйте
drop_pending_updates: trueпри восстановлении, если старые сообщения не критичны.
Безопасность и ограничение доступа
Webhook — публичный endpoint, поэтому защита обязательна. Основные меры:
- secret_token — первый и главный уровень защиты. Генерируйте длинную случайную строку и проверяйте её в каждом запросе.
- Фильтрация по IP. Telegram публикует диапазоны адресов, с которых отправляются запросы. Настройте файрвол или веб-сервер для блокировки запросов с других адресов. Помните, что диапазоны могут меняться — проверяйте документацию периодически.
- Rate limiting. Ограничьте число запросов с одного IP в единицу времени. Это защитит от DDoS, даже если злоумышленник узнает URL.
- HTTPS обязателен. Не используйте HTTP даже для тестирования в локальной сети: Telegram не примет такой адрес. Для разработки используйте long polling или туннель (ngrok, localtunnel) с действующим HTTPS.
- Не храните секреты в коде. Токен бота,
secret_tokenи параметры подключения к базе должны быть в переменных окружения или секретном хранилище (AWS Secrets Manager, HashiCorp Vault).
Чек-лист настройки webhook
Перед запуском в production проверьте:
- HTTPS-сертификат действителен и доверен публичным центром сертификации (или корректно передан самоподписанный).
- Сервер слушает один из разрешённых портов: 443, 80, 88 или 8443.
- URL содержит уникальный путь для каждого бота.
- Параметр
secret_tokenсгенерирован, сохранён в переменной окружения и передан вsetWebhook. - Endpoint проверяет заголовок
X-Telegram-Bot-Api-Secret-Tokenи возвращает HTTP 403 при несовпадении. - Endpoint возвращает
2xxсразу после commit Update в durable inbox, не ожидая бизнес-обработки. - Обработка обновлений выполняется в очереди (Celery, Bull, AWS SQS или аналог).
update_idпроверяется на дублирование с помощью Redis, базы данных или кеша в памяти.- Настроено логирование ошибок с записью
update_idи стека вызовов. - Настроен мониторинг
pending_update_countс алертом при превышении порога. - Файрвол ограничивает доступ к webhook по IP или настроен rate limiting.
- Проверен статус через
getWebhookInfo:urlсовпадает,pending_update_countравен нулю,last_error_dateотсутствует.
FAQ: частые вопросы о webhook
Можно ли использовать один webhook для нескольких ботов?
Технически можно направить несколько ботов на один домен с разными путями: https://example.com/webhook/bot1, https://example.com/webhook/bot2. Обработчик должен различать ботов по пути или токену в URL и использовать соответствующий secret_token для проверки. Не направляйте разные боты на один и тот же путь — вы не сможете определить источник обновления.
Что делать, если pending_update_count постоянно растёт?
Это значит, что ваш сервер не успевает обрабатывать обновления или возвращает ошибки. Проверьте логи на наличие исключений, увеличьте число воркеров в очереди, оптимизируйте запросы к базе данных, добавьте кеширование. Если старые обновления не критичны, вызовите setWebhook с параметром drop_pending_updates: true, чтобы сбросить очередь.
Как протестировать webhook локально?
Telegram требует публичный HTTPS-адрес, поэтому прямое подключение к localhost невозможно. Используйте туннель: ngrok (ngrok http 8000) или localtunnel (lt --port 8000) создают временный HTTPS-адрес, который перенаправляет запросы на локальный сервер. Полученный URL передайте в setWebhook. Альтернатива — разворачивайте тестовый сервер на облачной платформе (Heroku, Render, Fly.io) с бесплатным HTTPS.
Нужно ли удалять webhook перед обновлением кода?
Не обязательно. При rolling или blue-green deployment можно оставить webhook и положиться на повторную доставку. deleteWebhook нужен для явного перехода на getUpdates, а не как способ «остановить накопление». Параметр drop_pending_updates: true безвозвратно удаляет ожидающие Update, поэтому его включают только после осознанного решения отказаться от них.
Можно ли отправить Bot API-запрос прямо в ответе на webhook?
Да, официальный FAQ описывает возможность выполнения Bot API-запроса в ответе на webhook-обновление. Вместо обычного HTTP 200 вы возвращаете статус 200 с телом, содержащим JSON-объект метода (например, sendMessage). Telegram выполнит этот запрос от имени бота. Это работает для простых сценариев, но не заменяет очередь: если требуется несколько запросов или сложная логика, используйте асинхронную обработку.
Как часто Telegram повторяет доставку при ошибке?
Bot API не фиксирует точный алгоритм повторов и обещает лишь «разумное количество попыток». Проверяйте getWebhookInfo и мониторьте last_error_date, last_error_message и pending_update_count.
Практический вывод
Webhook требует HTTPS, setWebhook, непустого secret_token и явного allowed_updates. Endpoint атомарно записывает Update в durable inbox с уникальным update_id и лишь потом отвечает 2xx. Диспетчер не отмечает строку до успешного enqueue, а воркер делает бизнес-операции идемпотентными. Это закрывает гонку параллельных доставок и позволяет повторить сбой enqueue; абсолютную гарантию «ни одной потери и ни одного дубля» распределённая система не даёт, поэтому нужны мониторинг и разбор зависших строк.