Возьмём типичную эволюцию бота-магазина. На старте каталог живёт в inline-клавиатурах из урока про inline-кнопки: бот шлёт список товаров, пользователь листает страницами, корзина собирается в callback data. Пока товаров двадцать, схема работает. Потом появляются фильтры, поиск по названию, галерея из шести фото на товар — и бот превращается в болото: каждую страницу надо собрать руками, каждое состояние удержать в карте, а пользователь теряется в сообщениях, которые сам же нагенерировал. Telegram придумал для этого сценария прямой ответ: Mini App — веб-приложение, которое открывается прямо в клиенте, по кнопке, без выхода из чата и без установки чего-либо. Бот при этом остаётся ботом: он всё так же слушает апдейты и отвечает сообщениями, просто часть интерфейса переезжает на страницу. В этом уроке — обзорная карта механизма: кнопка, передача данных, подпись токеном и граница применимости, за которой Mini App скорее вредит, чем помогает.
Webapp-кнопка: как устроен вход в Mini App#
Telegram mini app начинается не с кода, а с кнопки: специальная кнопка в клавиатуре несёт URL, и по нажатию клиент Telegram открывает этот адрес в встроенном webview. В библиотеке go-telegram-bot-api для inline-клавиатуры есть конструктор tgbotapi.NewWebAppButton(text, url) — он возвращает InlineKeyboardButton с заполненным полем WebApp. Тот же механизм поддерживает и reply-клавиатура: у KeyboardButton есть поле WebApp, и webapp-кнопка размещается внизу экрана наравне с обычными. Требование одно и жёсткое: URL обязан быть HTTPS — Telegram не откроет http-адрес, и попытка отправить такую кнопку вернёт ошибку на этапе Send, ещё до пользователя. Второе практическое следствие: страница Mini App — это отдельный проект. Бот может отправлять кнопку из любого места кода, но сам интерфейс живёт на вашем хостинге, и его деплой подчиняется обычным правилам веб-разработки, а не логике Bot API.
Webapp-кнопка в inline-клавиатуре
GOkeyboard := tgbotapi.NewInlineKeyboardMarkup(
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewWebAppButton("Открыть каталог",
"https://shop.example.com/app"),
),
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("Помощь", "help"),
),
)
msg := tgbotapi.NewMessage(chatID, "Каталог и управление — в одной кнопке:")
msg.ReplyMarkup = keyboard
_, _ = bot.Send(msg)Webapp-кнопка соседствует с обычными inline-кнопками в одной клавиатуре без ограничений: в примере верхний ряд открывает приложение, нижний работает по классической callback-схеме из урока про inline-клавиатуры. Send отправляет сообщение как обычно; если URL не https, Telegram отклонит запрос с ошибкой про кнопку — поймать её проще сразу в логах, чем от пользователя.
Что умеет Mini App: тема, MainButton и sendData#
Внутри webview страница получает доступ к интерфейсу Telegram через объект window.Telegram.WebApp — это официальный JavaScript-мост, и он даёт больше, чем кажется. Приложение знает тему клиента через colorScheme и подстраивает палитру, умеет вызывать лёгкую вибрацию hapticFeedback, раскрываться на весь экран и пользоваться CloudStorage — хранилищем, привязанным к пользователю и боту, без вашего бэкенда для мелких настроек. Главный элемент управления — MainButton: большая кнопка внизу окна с вашим текстом, по которой приложение что-то делает: сохраняет форму, отправляет заказ, подтверждает действие. А для связи с ботом есть sendData: приложение вызывает его со строкой, и в чат боту прилетает обычное Message с заполненным полем WebAppData — там ваша строка и текст кнопки. Это единственный канал из Mini App в бота без собственного HTTP-сервера, и он закрывает большинство сценариев: форма отправлена — бот получил данные и отвечает в чат.
initData: подпись токеном на бэкенде#
При открытии Telegram передаёт приложению строку initData — в ней лежат id пользователя, имя, краткое имя, фото, auth_date и ещё несколько полей, плюс hash. Строка доступна странице без всяких запросов, и соблазн поверить ей немедленно велик. Но initData — это просто данные клиента: любой человек может открыть тот же URL вне Telegram, в обычном браузере, и подставить туда чужой id. Доверие здесь даёт ровно один механизм — подпись. Проверка на бэкенде устроена так: из initData берутся все поля кроме hash, сортируются по ключам и склеиваются в строку проверки; секрет выводится как HMAC-SHA256 от вашего ботового токена с сообщением WebAppData; затем считается HMAC-SHA256 от строки проверки уже с этим секретом и сравнивается с hash из initData. Совпало — данные пришли из Telegram и не искажены по дороге. Это обзорная схема урока: точный порядок склейки и форматы полей зафиксированы в документации Bot API, и код ниже показывает шаги честно, без притворства, что строка проверки строится сама.
Шаги проверки initData в Go
GO// 1. Секрет выводится из токена бота — именно так, а не конкатенацией
mac := hmac.New(sha256.New, []byte(botToken))
mac.Write([]byte("WebAppData"))
secret := mac.Sum(nil)
// 2. Строка проверки: поля initData без hash, отсортированные
// по ключу, в виде key=value, соединённые переводом строки
// 3. Сравнение подписи
check := hmac.New(sha256.New, secret)
check.Write([]byte(dataCheckString))
if subtle.ConstantTimeCompare(check.Sum(nil), hashBytes) == 1 {
// подпись верна: пользователь и его данные настоящие
}
// 4. Свежесть: поле auth_date — юникс-время открытия приложения
// если оно старше суток, initData обычно отвергаютСравнение делается через subtle.ConstantTimeCompare из пакета crypto/subtle — сравнение строк обычным == допускает тайминг-атаки, и для проверки подписи это не паранойя, а стандарт. Отдельно от подписи проверяется auth_date: initData, скопированная неделю назад, подпись пройдёт — подпись гарантирует источник, а не свежесть.
Самый частый инцидент с Mini App выглядит одинаково: фронтенд читает initData, проверяет, что hash присутствует, и шлёт всё на бэкенд; бэкенд берёт из строки user id и открывает данные этого пользователя. Через неделю к владельцу приходит пользователь с чужим балансом: в обычном браузере любой человек открывает ту же страницу, руками дописывает себе чужой id — и бэкенд послушно отвечает. Проверка существования hash не защищает ни от чего: hash без валидации — просто строка. Граница у честности есть: приложение, которое только показывает публичный контент — каталог без заказов, справочник, лендинг, — может пережить отсутствие проверки, там нечего красть. Всё, что привязано к аккаунту — баланс, история, платежи, личные сообщения, — валидацию initData на бэкенде обязано делать с первого дня, и дешевле прикрутить её до релиза, чем объяснять утечку.
Когда Mini App не нужен#
Спорное мнение, которым этот урок и живёт: в большинстве ботов Mini App — лишний проект. Веб-интерфейс внутри Telegram — это второй фронтенд: HTML, JavaScript, сборка, HTTPS-хостинг, отдельный деплой и отдельное место, где всё ломается. Если бот решает задачу клавиатурами и диалогом — анкета по шагам из урока про состояние, меню на inline-кнопках, каталог с листанием — то Mini App добавляет неделю работы ради эффекта, который пользователь не заметит. Моя граница простая и проверенная: пока каждый экран вашего бота умещается в одно сообщение с клавиатурой, Mini App не нужен. Он становится оправданным, когда интерфейс упирается в потолок сообщений: фильтры и поиск в реальном времени, корзина с пересчётом на лету, графики и дашборды, загрузка файлов через привычный input, таблицы на сотни строк. Второй честный случай — брифинг и формы длиннее пяти шагов: в сообщениях их терпят, в приложении проходят. Внутри этой границы — клавиатуры, за ней — webapp-кнопка.
Бот-сообщения против Mini App#
| Задача | Инструмент | Почему |
|---|---|---|
| Анкета из трёх-пяти шагов | FSM и клавиатуры | Состояние диалога делает это бесплатно |
| Меню и навигация | Inline-клавиатуры | EditMessageText перерисовывает экраны в одном сообщении |
| Каталог с листанием | Inline-клавиатуры | До десятков товаров хватает callback data |
| Поиск, фильтры, корзина | Mini App | Интерактив на лету в сообщениях не живёт |
| Графики и дашборды | Mini App | Рисование и масштабирование — работа веб-страницы |
| Загрузка файлов из UI | Mini App | Нативный выбор файлов через input |
Практический порядок внедрения короткий. Сначала страница: простой фронтенд, который подключает скрипт Telegram WebApp и умеет вызывать sendData — для первого прототипа хватит одной формы. Потом хостинг: адрес обязан быть HTTPS, и на этапе разработки выручает туннель, который даёт публичный https-адрес локальному серверу. Затем кнопка: NewWebAppButton в клавиатуру бота, проверка на своём клиенте. И последним — валидация initData на бэкенде вместе с проверкой auth_date: подпись сверяется на каждом запросе, где приложение действует от имени пользователя. Апдейт с WebAppData обрабатывается в обычном switch по update.Message — отдельный транспорт не нужен, и если бот уже переведён на webhook из урока про webhook-сервер, новых точек входа не появляется. Если же панель приложения должна знать состояние бота — корзину, напоминания, подписку — данные отдаются мини-приложению вашим API, и здесь пригодится дисциплина аутентификации из урока про middleware в jwt-курсе.
Код проверки подписи и работа с HMAC требуют локального Go: песочница этого самоучителя поддерживает только fmt, strings, strconv, math, sort, errors и time, поэтому crypto/hmac и HTTP-запросы в браузере не запустить. Для кнопки тоже нужен реальный адрес: Telegram не откроет даже localhost, так что на время теста сервер выводится наружу туннелем с https. Дальше сценарий простой: webapp-кнопка открывает страницу, sendData отправляет строку боту, лог хендлера печатает WebAppData — и весь цикл становится видимым за вечер.
Проверка: требования к кнопке
Какой URL допустимо передать в tgbotapi.NewWebAppButton?
Проверка: секрет подписи
Из чего выводится секрет, которым подписана initData?
Кнопка с http-адресом
Прочитайте код и выберите, что он выведет:
kb := tgbotapi.NewInlineKeyboardMarkup(
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewWebAppButton("Панель",
"http://panel.example.com/"),
),
)
msg := tgbotapi.NewMessage(chatID, "Открыть панель:")
msg.ReplyMarkup = kb
_, err := bot.Send(msg)Соберите webapp-кнопку
Впишите конструктор кнопки, поле клавиатуры и метод отправки.
Соберите приём данных из Mini App
Расставьте строки хендлера: от получения WebAppData до ответа в чат.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: Mini App
Маршрут вокруг этой темы такой: девятый урок про inline-клавиатуры — база, на которой стоит webapp-кнопка, и десятый урок про состояние диалога — то, что закрывает формы без всякого Mini App. Дальше по курсу: следующий урок про реакции и стикеры — эмоции, которые стоят дешевле любого веб-интерфейса; урок про тестирование бота — как покрыть ветку WebAppData тестом без Telegram; урок про деплой — systemd, после которого HTTPS-хостинг для страницы станет соседней задачей. Проверку подписи initData удобно оформить как middleware в духе jwt-курса — тот же приём, другая подпись. Полный маршрут — на странице курса.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- Mini App — это https-страница, которую Telegram открывает по webapp-кнопке: tgbotapi.NewWebAppButton в inline- или reply-клавиатуре.
- Страница получает тему, haptic feedback, MainButton и CloudStorage, а sendData возвращает данные боту обычным Message с полем WebAppData.
- initData валидируется подписью: секрет из HMAC(токен, WebAppData), проверка HMAC(секрет, строка проверки), сравнение за константное время.
- Подпись гарантирует источник, а не свежесть — рядом всегда проверяется auth_date.
- Бэкенд, который верит непроверенному user id, отдаёт чужие данные: валидация обязательна для всего, что связано с аккаунтом.
- Пока интерфейс умещается в сообщения и клавиатуры, Mini App — лишний проект; он оправдан поиском, корзиной, графиками и файлами.
Домашнее задание
Соберите минимальный цикл Mini App: страница с одной формой и вызовом sendData, https-адрес через туннель, webapp-кнопка в боте через NewWebAppButton, хендлер Message с WebAppData, который печатает полученную строку и отвечает в чат. Затем добавьте проверку initData на бэкенде: HMAC-секрет из токена, строка проверки из отсортированных полей, сравнение через ConstantTimeCompare, отвержение auth_date старше суток. В конце попробуйте открыть страницу в обычном браузере с подменённым user id и убедитесь, что без подписи бэкенд её отвергает.
Личные заметкиПусто
Вопросы и ответы по уроку
Нужен ли отдельный сервер для Mini App?
Нужен любой https-хостинг для страницы — статический или с бэкендом, это отдельное решение. Сам бот при этом остаётся на своём процессе: апдейты с WebAppData приходят в общий цикл, новых портов и серверов для бота не появляется.
Можно ли проверить initData силами библиотеки go-telegram-bot-api?
Проверка подписи — это стандартный HMAC, который честнее написать руками в двадцать строк и понимать каждый шаг. Библиотека занимается Bot API, а initData валидируется на вашем бэкенде до обращения к Telegram; готовые реализации есть в сообществе, но алгоритм из документации проще сверить руками.
Что вернётся боту после sendData?
Обычный Message с полем WebAppData, в нём ваша строка и текст кнопки приложения. Обработайте ветку в общем switch: данные короткие, всё серьёзное приложение делает через ваш собственный API — sendData закрывает только подтверждения и простые действия.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
Мониторинг Go с Prometheus: метрики, Grafana, алерты
Курс по метрикам в Go: Counter, Gauge, Histogram, лейблы, PromQL, Grafana-дашборды, алерты и бизнес-метрики. 25 уроков с примерами client_golang.
- Понимать четыре типа метрик и выбирать правильный
- Экспортировать метрики из Go через promhttp
- Инструментировать HTTP-запросы и бизнес-события
- Писать PromQL-запросы: rate, sum, p99