Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 18 из 25 ~20 мин +20 XP

Webhook: бот как HTTP-сервер

Webhook переводит telegram-бота из режима опроса в режим сервиса: Telegram сам приносит обновления POST-запросами на ваш HTTPS-адрес, и бот работает как обычный HTTP-сервер на net/http. Разберём SetWebhook и ListenForWebhook в tgbotapi, токен как секрет пути, серты и порты, конфликт с long polling и честное сравнение двух способов получать апдейты.

Содержание урока · 9 разделов

Все пятнадцать предыдущих уроков бот жил на ноутбуке и работал в режиме long polling: цикл сам ходил в Telegram за обновлениями и ждал. Это удобно на старте, но у модели есть цена, которая становится заметной на сервере: постоянные исходящие запросы, задержка между событием и обработкой, невозможность масштабировать на два инстанса — апдейты уйдут в одного из поллингов, и никто не знает в какого. Webhook переворачивает стрелку: вы один раз говорите Telegram свой HTTPS-адрес командой setWebhook, и с этого момента каждое событие — сообщение, нажатие кнопки, inline-запрос — приезжает к вам POST-запросом с JSON-апдейтом в теле. Бот становится обычным HTTP-сервисом: порт, хендлер, nginx при желании, горизонтальное масштабирование за балансировщиком. Хорошая новость для тех, кто прошёл первые уроки: цикл обработки апдейтов не меняется вообще — меняется только источник, из которого цикл читает. В этом уроке разберём настройку на Go, подводный камень с включённым polling, серты и порты, отладку без Telegram и путь переезда на сервер.

Как устроен webhook#

Механика двухшаговая. Сначала бот сообщает Telegram адрес: конфигурация NewWebhook с HTTPS-URL передаётся методом Request — это отправка setWebhook на сервер. Дальше Telegram на каждое событие шлёт POST на этот адрес, и телом запроса лежит тот самый JSON-апдейт, который long polling получал в канале. Адрес обязан быть HTTPS: сервер отклонит http-URL ошибкой про то, что для вебхука требуется HTTPS. Сертификат подходит любой из двух: валидный от доверенного центра вроде Let's Encrypt или самоподписанный, который прикладывается к setWebhook через NewWebhookWithCert. Путь в URL — это ваш единственный барьер от подделок, и стандартная практика — использовать сам токен бота: Telegram знает полный адрес, посторонний не угадает, и каждый входящий запрос можно доверять по факту пути. Порт тоже ограничен: документация называет 443, 80, 88 и 8443 — слушать сервером что-то другое Telegram откажется.

Бот-сервер целиком

GO
func main() {
        bot, err := tgbotapi.NewBotAPI(os.Getenv("BOT_TOKEN"))
        if err != nil {
                log.Fatal(err)
        }

        wh := tgbotapi.NewWebhook("https://example.com/" + bot.Token)
        if _, err := bot.Request(wh); err != nil {
                log.Fatal(err)
        }

        updates := bot.ListenForWebhook("/" + bot.Token)
        go http.ListenAndServe("0.0.0.0:8080", nil)

        for update := range updates {
                if update.Message != nil {
                        log.Println(update.Message.Text)
                }
        }
}

Три сущности: конфигурация NewWebhook уходит методом Request — это и есть setWebhook, затем ListenForWebhook регистрирует приёмник по пути с токеном и возвращает канал, а HTTP-сервер поднимается в горутине. Main занят привычным циклом range по апдейтам — тот же код, что работал с long polling, читает новый источник.

ListenForWebhook под капотом#

Метод ListenForWebhook делает ровно две вещи и обе важны для понимания. Первая: он регистрирует HTTP-хендлер на переданном пути в стандартном роутере net/http — каждый POST на этот путь будет разобран. Вторая: в хендлере тело запроса декодируется из JSON в структуру tgbotapi.Update и отправляется в буферизованный канал, который метод возвращает вызывающему. Отсюда два практических правила. Первое — порядок строк: ListenForWebhook вызывается до старта HTTP-сервера, иначе сервер начнёт принимать запросы раньше, чем хендлер зарегистрирован, и первые апдейты упадут в 404. Второе — цикл range из уроков про polling работает без единой правки: источник сменился, интерфейс остался, и весь код обработки сообщений, callback и inline-запросов переезжает как есть. Буфер канала сглаживает всплески: Telegram шлёт апдейты параллельно и не ждёт, пока ваш цикл до них дойдёт, — при длительном заторе сервер начнёт возвращать ошибки, и Telegram будет повторять доставку.

HTTPS, порты и локальная отладка#

Главное неудобство webhook — проверка живьём требует публичного HTTPS-адреса, которого у локальной разработки нет. Рабочие варианты три. Первый — туннель: ngrok или ssh-туннель до сервера дают временный HTTPS-адрес, который на время отладки скармливается NewWebhook. Второй — curl: Telegram шлёт обычный POST с JSON, и тот же запрос можно сделать руками на localhost — хендлер получит апдейт и канал отработает, а Telegram для этого не нужен. Третий — сразу деплой на тестовый VPS с нормальным сертификатом, и здесь помогает связка с уроком про деплой. Токен в пути стоит держать сквозным: от Telegram до вашего хендлера, через nginx, если он есть, — тогда проверка подлинности остаётся в одном месте. Для продакшена конфигурация уточняется: AllowedUpdates срезает ненужные типы апдейтов ещё на стороне Telegram, а MaxConnections ограничивает параллельные соединения доставки.

Уточнённая настройка и сервер

GO
wh := tgbotapi.WebhookConfig{
        URL:            "https://example.com/" + bot.Token,
        AllowedUpdates: []string{"message", "callback_query"},
}
if _, err := bot.Request(wh); err != nil {
        log.Fatal(err)
}

server := &http.Server{
        Addr:         "127.0.0.1:8080",
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
}

updates := bot.ListenForWebhook("/" + bot.Token)
go server.ListenAndServe()

Вместо конструктора здесь собрана структура WebhookConfig: AllowedUpdates говорит Telegram присылать только сообщения и нажатия, остальная почта отрезается до отправки. Сервер — не голый ListenAndServe, а http.Server с таймаутами на чтение и запись: webhook-доставка это короткие POST, и таймауты защищают от висящих соединений.

Кейс: переезд бота на VPS с nginx#

Типовой переезд выглядит так. На VPS ставится nginx, который завершает TLS на порту 443 с сертификатом Let's Encrypt, и проксирует запросы на внутренний адрес 127.0.0.1:8080, где живёт Go-сервер бота. NewWebhook получает внешний адрес вида https://bot.example.com/ с токеном в пути, nginx передаёт его как есть, и проверка подлинности остаётся в коде бота. Бот запускается юнитом systemd — настройкам автозапуска посвящён отдельный урок про деплой, — и после каждого рестарта setWebhook заново отправлять не нужно: Telegram хранит адрес до смены. Первые сутки после переезда стоит смотреть два лога: access-лог nginx на предмет посторонних запросов к пути с токеном — их не должно быть, а единичные можно молча отдавать 403 — и лог бота на предмет всплесков. Ежедневные апдейты приезжают параллельно, без длинных ожиданий, и латентность ответа бота падает с сотен миллисекунд polling до единиц миллисекунд доставки.

Возврат к polling

GO
// снять вебхук и вернуться к long polling
if _, err := bot.Request(tgbotapi.DeleteWebhookConfig{}); err != nil {
        log.Fatal(err)
}

u := tgbotapi.NewUpdate(0)
u.Timeout = 30
updates := bot.GetUpdatesChan(u)

DeleteWebhookConfig снимает регистрацию адреса, и апдейты снова накапливаются на сервере Telegram до запроса GetUpdates. Дальше включается привычный канал из пятого урока: NewUpdate с офсетом и тайм-аутом, GetUpdatesChan и тот же цикл. Переключение между режимами стоит несколько строк, если код обработки вынесен отдельно от источника.

Когда polling всё ещё лучше#

Переехав на webhook, честно признать и обратную сторону. Пока бот живёт на ноутбуке, за NAT или в песочнице, у вас нет публичного HTTPS-адреса — и polling остаётся единственным способом получать апдейты без обвязки туннелями. Для отладки polling удобнее ещё и потому, что апдейты накапливаются на стороне Telegram: выключили процесс на ночь, включили утром — очередь пришла вся, а с вебхуком часть событий Telegram повторит, а часть отбросит. Небольшому боту на сотню пользователей вебхук ничего не даёт, кроме обязательного серта и лишнего компонента в схеме. Мой рабочий порог — переезд на сервер и аудитория в тысячи чатов: раньше этого polling не мешал никому, и половина продовых ботов спокойно живёт на нём годами. Переключение стоит пять строк, если источник апдейтов вынесен за обработку, так что решение не финальное и легко пересматривается в обе стороны.

Long polling против webhook#

ПараметрLong pollingWebhook
Кто инициируетБот ходит за апдейтамиTelegram приносит POST
АдресНе нужен, работает из ноутбукаПубличный HTTPS с сертом
ПортыЛюбые443, 80, 88, 8443
ОтладкаПроще: ничего кроме токенаТуннель или curl руками
МасштабированиеОдин поллер на ботаНесколько инстансов за балансировщиком
Когда выбиратьРазработка, первые проектыПрод, аудитория, интеграции
Повтор доставкиОчередь копится на стороне TelegramПовторы несколько часов, потом отброс

Проверка: что делает ListenForWebhook

Что возвращает метод bot.ListenForWebhook("/" + token)?

Проверка: зачем токен в пути

Зачем в путь вебхука кладут токен бота?

Что ответит Telegram на http-адрес

Прочитайте код и выберите, что он выведет:

wh := tgbotapi.NewWebhook("http://example.com/hook")
_, err := bot.Request(wh)
fmt.Println(err)

Соберите включение вебхука

Впишите конструктор, поле токена и методы регистрации.

wh := tgbotapi.("https://example.com/" + bot.) bot.(wh) updates := bot.("/" + bot.Token)

Шесть строк перехода на webhook

Расставьте запуск бота-сервера: от создания клиента до цикла апдейтов.

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Карточки: webhook

Термин

setWebhook / NewWebhook

нажмите, чтобы перевернуть
Определение

Регистрация HTTPS-адреса доставки: Telegram начинает слать апдейты POST-запросами. Конфигурация уходит через bot.Request, не через Send.

1 / 6

Бот стал HTTP-сервисом, и дальше маршрут раздваивается. Если остаётесь в режиме приёма, следующий шаг — фоновые задачи: урок про напоминания добавляет боту горутину с тикером, которая работает независимо от апдейтов. Обслуживание на сервере — сборка и systemd — разобраны в уроке про деплой. Откуда вебхук растёт: механика long polling и её ограничения остались в пятом уроке, а обвязка HTTP-серверов на Go с роутерами и middleware — тема курса по chi. Полный маршрут курса — на его странице.

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Webhook переворачивает направление: не бот ходит за апдейтами, а Telegram приносит их POST-запросами на HTTPS-адрес.
  • setWebhook отправляется конфигурацией NewWebhook через bot.Request; ListenForWebhook регистрирует приёмник и возвращает канал.
  • Цикл range по апдейтам не меняется: источник сменился, код обработки переезжает как есть.
  • Путь с токеном — секрет доставки: посторонний POST не угадает адрес, а порты ограничены 443, 80, 88 и 8443.
  • Polling и webhook не работают одновременно: GetUpdates отвечает 409 Conflict, возврат — через DeleteWebhook.
  • HTTPS обязателен: валидный сертификат, самоподписанный через NewWebhookWithCert или туннель для отладки.
  • AllowedUpdates и http.Server с таймаутами — минимальная прод-настройка приёма.
  • Отладка без Telegram: сохранённый JSON-апдейт уходит в хендлер через curl, и канал отрабатывает как настоящий.

Домашнее задание

Переведите локального бота на webhook с туннелем или проверьте хендлер через curl с сохранённым JSON-апдейтом: NewWebhook с адресом и токеном в пути, Request на setWebhook, ListenForWebhook до старта сервера, http.Server с таймаутами в горутине. Убедитесь, что цикл range обрабатывает апдейты без правок, затем попробуйте запустить рядом polling-процесс и поймайте в логе ошибку 409 Conflict — теперь вы знаете её в лицо. Завершите упражнение возвратом: DeleteWebhookConfig и polling из пятого урока снова работает.

Личные заметкиПусто
Хранятся только в вашем браузере (Telegram · Webhook: бот как HTTP-сервер)

Вопросы и ответы по уроку

Нужен ли вебхук, если бот маленький и живёт на ноутбуке?

Нет: для разработки и первых проектов long polling проще — не нужен ни адрес, ни сертификат, ни открытый порт. Вебхук окупается на сервере: прод-аудитория, интеграции, желание масштабироваться и измерять латентность доставки. Переключение туда-обратно стоит несколько строк, если источник апдейтов вынесен за обработку.

Можно ли слушать произвольный порт, например 3000?

Telegram принимает доставку только на 443, 80, 88 и 8443. Ваше приложение может слушать любой порт внутри — например, 8080 за nginx, — но внешний вход для Telegram должен заканчиваться на одном из разрешённых портов.

Что происходит с апдейтами, если сервер был недоступен?

Telegram несколько часов повторяет неудавшуюся доставку с отступающими интервалами, потом отбрасывает апдейт. Порядок при этом сохраняется по чату, но не гарантируется глобально. Для критичных к потере задач бот обязан дублировать состояние у себя — очередь в Redis или журнал событий — и не полагаться только на доставку Telegram.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Prometheus25 уроков~12 чпрактика25 уроков уже доступны

Мониторинг Go с Prometheus: метрики, Grafana, алерты

Курс по метрикам в Go: Counter, Gauge, Histogram, лейблы, PromQL, Grafana-дашборды, алерты и бизнес-метрики. 25 уроков с примерами client_golang.

  • Понимать четыре типа метрик и выбирать правильный
  • Экспортировать метрики из Go через promhttp
  • Инструментировать HTTP-запросы и бизнес-события
  • Писать PromQL-запросы: rate, sum, p99