
Telegram Mini App это веб-страница, которую Telegram открывает внутри себя. Не отдельное приложение из магазина, не сайт в браузере: тот же HTML, CSS и JavaScript, только окно им выдаёт мессенджер, а вместе с окном имя того, кто его открыл. Человек нажимает кнопку в чате и сразу оказывается в приложении, не устанавливая ничего и не вводя пароль.
Дальше по делу: из чего оно собирается, где ломается и что нужно, чтобы запустить своё сегодня вечером. В конце лежит рабочий шаблон на Python, бот с панелью управления прямо внутри Telegram. Скачивается и запускается за десять минут.
Бот это диалог. Сообщения, кнопки под ними, иногда меню команд. Всё, что человек делает, он делает по очереди: выбрал, ответил, подождал, снова выбрал.
Mini app это экран. Карточки товаров с картинками, корзина, форма, таблица, календарь, что угодно из веба. Человек видит всё сразу и трогает то, что хочет, а не то, что ему предложили следующим сообщением.
Интерфейс
Сообщения и кнопки под ними
Полноценная веб-страница
Порядок действий
По очереди, шаг за шагом
Всё на экране, в любом порядке
Что видно
Текст и картинки
Вёрстка, анимация, таблицы, формы
Где выполняется код
Только на сервере
В браузере Telegram плюс сервер
Трудоёмкость
Ниже
Примерно вдвое выше
Когда достаточно
Заявка, уведомление, короткий опрос
Каталог, корзина, кабинет, отчёт
Правило выбора простое. Если весь сценарий укладывается в три-четыре вопроса подряд, берите бота: mini app там будет из пушки по воробьям. Под этот случай у нас лежит отдельный шаблон разговорного бота, там ни фронтенда, ни панели нет вовсе. Если человеку нужно сравнить варианты, вернуться назад, поменять решение и увидеть итог, берите mini app, диалог такое поддерживает плохо.
Про удвоение я не преувеличиваю. Задача выглядит простой, потом хочется добавить визуал, и по моим проектам время уходит вдвое, иногда больше. Mini app ровно этот случай: к серверной логике добавляется целый фронтенд со своей вёрсткой, состояниями и проверками.
Технически почти ничем. Это тот же фронтенд, который вы бы выложили на любой хостинг. Отличия начинаются там, где заканчивается вёрстка.
Он знает, кто пришёл. Обычный сайт видит анонима, и чтобы узнать человека, нужна регистрация: почта, пароль, письмо со ссылкой. Mini app получает идентификатор пользователя от самого Telegram, в момент открытия. Ни формы, ни подтверждения, ни забытого пароля.
Своего адреса у него для человека нет. Технический URL, конечно, есть, но пользователь его не видит и не вводит. Он попадает внутрь через бота или по ссылке вида t.me/имя_бота/имя_приложения.
И его нельзя найти поиском. Обычный сайт можно найти в Google или Яндексе. Mini app искать негде: он живёт внутри мессенджера, поисковики туда не ходят. Трафик приходит из чатов, каналов и рекомендаций. Сайт можно построить так, чтобы клиенты приходили сами, а к mini app приходят только те, кому вы дали ссылку.

Четыре части, и ни одну нельзя выкинуть.
Бот. Он нужен обязательно, даже если делать в чате нечего. Приложение привязано к боту, кнопка запуска живёт в боте, и токен бота участвует в проверке подписи.
Фронтенд. Обычная страница: HTML, CSS, JavaScript. Плюс один скрипт от Telegram, который даёт данные о пользователе, тему клиента и системные кнопки.
Сервер. Отдаёт эту страницу и обслуживает её запросы. Именно он проверяет подпись, считает деньги и хранит заказы.
Адрес по https. Тип данных, которым задаётся приложение, в документации Bot API описан прямо: «An HTTPS URL of a Web App to be opened». Без сертификата Telegram приложение не откроет, http работает только в тестовой среде.
Бот и сервер при этом могут жить в одном процессе. В шаблоне так и сделано: до нескольких заказов в секунду разделять их незачем. Общего состояния в памяти у них нет, всё через базу, поэтому разъехаться по разным машинам они смогут потом без переписывания.
Бот, адрес по https, Python. Всё бесплатное, всё за один вечер.
К @BotFather за токеном, это полминуты: команда /newbot, он возвращает строку вида 123456789:AAG. Дальше туннель. Пока приложение живёт на вашем ноутбуке, постоянного адреса у него нет, а туннель поднимает публичный https-адрес и заворачивает его на локальный порт. Адрес меняется при каждом перезапуске, и для отладки это нормально.
Из пакетов понадобятся два: aiogram для бота и aiohttp для веб-части. Остальное уже в стандартной библиотеке, включая базу данных. Третий пакет нужен, только если включаете ответы нейросетью.
Про версию Python стоит знать заранее. Свежий aiogram требует 3.10 или новее, и на 3.9 установка пройдёт, но поставится не то, что вы ждёте: на нашей проверке подтянулась версия 3.22 вместо актуальной 3.31. Работать будет, обновляться перестанет.
Магазина приложений, модерации и ожидания ревью в этой схеме нет вообще. Первый работающий экран внутри Telegram появляется в тот же вечер.
Вот весь бот, который открывает mini app. Один обработчик и одна кнопка, библиотека aiogram 3.
import asyncio
from aiogram import Bot, Dispatcher
from aiogram.filters import CommandStart
from aiogram.types import (
InlineKeyboardButton,
InlineKeyboardMarkup,
Message,
WebAppInfo,
)
dp = Dispatcher()
@dp.message(CommandStart())
async def start(message: Message) -> None:
button = InlineKeyboardButton(
text="Открыть меню",
web_app=WebAppInfo(url="https://ваш-адрес"),
)
await message.answer(
"Меню открывается прямо здесь, без перехода в браузер.",
reply_markup=InlineKeyboardMarkup(inline_keyboard=[[button]]),
)
asyncio.run(dp.start_polling(Bot(token="ТОКЕН")))Ключевая строка тут одна: web_app=WebAppInfo(url=...). Обычная кнопка отправляет текст, эта открывает приложение и передаёт ему данные о пользователе.
Дальше на стороне страницы вы эти данные забираете.
const tg = window.Telegram.WebApp; tg.ready(); // сообщаем клиенту, что интерфейс готов tg.expand(); // разворачиваем на всю высоту // Строка с данными о пользователе и подписью Telegram. // Её нужно отправить на сервер и проверить ТАМ. const initData = tg.initData;
В примере выше именно инлайн-кнопка, а не кнопка обычной клавиатуры. Разница выглядит косметической, а стоит всей авторизации.
Документация Telegram про данные, которые приходят в приложение, говорит прямо: они пустые, если приложение запущено с кнопки клавиатуры или из инлайн-режима. То есть приложение откроется, картинка будет на месте, а подписанных данных о пользователе в нём не окажется вообще.
Инлайн-кнопка
Кнопка под сообщением бота
Есть
Кнопка меню
Слева от поля ввода
Есть
Прямая ссылка
t.me/бот/приложение
Есть
Кнопка клавиатуры
Обычная клавиатура под чатом
Нет
Инлайн-режим
Вызов бота из любого чата
Нет
Кнопка клавиатуры чаще попадается в примерах: она проще выглядит в коде. Всё кажется рабочим ровно до момента, когда на сервере появляется проверка подписи и начинает отклонять каждый запрос. Дальше человек два часа ищет ошибку в алгоритме, которого в этом месте нет.
Вывод короткий: инлайн-кнопка или кнопка меню слева от поля ввода. Остальные способы годятся, если приложению вообще не нужно знать, кто пришёл.

Telegram кладёт в страницу строку initData. Внутри лежит идентификатор пользователя, его имя, время открытия и подпись.
Соблазн понятный: взять оттуда user.id, отправить на сервер и работать. Проблема в том, что страница выполняется на устройстве пользователя, и всё, что в ней лежит, редактируется из консоли браузера. Подставить чужой идентификатор занимает одну строку и десять секунд. Дальше человек оформляет заказы от чужого имени, видит чужую историю и меняет чужие данные.
От этого и защищает подпись. Telegram считает её по вашему токену бота, который знаете только вы и Telegram. Ваш сервер пересчитывает подпись и сравнивает. Совпало, данные настоящие. Не совпало, запрос отклоняется.
import hashlib
import hmac
from urllib.parse import parse_qsl
def check(init_data: str, bot_token: str) -> dict:
pairs = dict(parse_qsl(init_data))
received = pairs.pop("hash")
# Пары сортируются по ключу и склеиваются через перевод строки.
check_string = "\n".join(f"{k}={pairs[k]}" for k in sorted(pairs))
# Внимание на порядок: ключ - строка "WebAppData", сообщение - токен.
secret = hmac.new(b"WebAppData", bot_token.encode(), hashlib.sha256).digest()
expected = hmac.new(secret, check_string.encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, received):
raise ValueError("подпись не совпала")
return pairsПромахиваются почти всегда в одних и тех же трёх местах.
Порядок аргументов в предпоследней строке. Ключ это литерал WebAppData, сообщение токен бота. Поменяете местами: подпись всё равно посчитается, самодельные тесты пройдут, а настоящий Telegram получит отказ. Ошибка неприятна именно тем, что выглядит как работающий код.
Проверка возраста. В данных есть поле auth_date со временем открытия. Без него строка, однажды подсмотренная, работает вечно. Официального срока годности у неё нет: документация предлагает дополнительно проверить это поле и не называет ни одного числа. Все «initData живёт 24 часа» из туториалов это выбор их авторов, а не требование Telegram. Предел выбираете вы; в шаблоне стоят сутки, и это компромисс с приложением, которое провисело открытым в фоне.
Кодирование значений. Подпись считается по раскодированным значениям, а в самой строке они закодированы. Перепутаете, и получите приложение, которое падает у пользователя с кириллицей в имени и прекрасно работает у вас. Я на это наступил при сборке шаблона: строка с непреобразованными символами не уходит даже в HTTP-заголовок, браузер отказывается её отправлять.
Идентификатор проверяется подписью. Цена берётся из каталога на сервере. Количество ограничивается сверху и снизу. Ни одно из трёх нельзя доверить браузеру.
Подпись закрывает вопрос «кто это». Дальше начинается вопрос «что ему можно», и его браузер решать тоже не должен.
Витрина с корзиной. Логика та же, что с идентификатором: браузер и так знает цены, он их показывает, пусть он и считает итог, а сервер запишет готовое число. Но браузер находится в руках покупателя. Открыть консоль и отправить заказ с ценой в одну копейку занимает столько же времени, сколько подставить чужой идентификатор. Правильно так: фронт присылает идентификатор товара и количество, всё остальное сервер берёт из своего каталога. То же с количеством: отрицательное даёт отрицательную сумму, а миллион штук это заказ, который никто не выполнит, но который займёт место в базе.
Панель управления. Спрятать вкладку в интерфейсе недостаточно: запросы к серверу уходят напрямую, минуя кнопки. Проверка «этот человек владелец» должна стоять в каждом обработчике, а не в разметке. В шаблоне панель управления ботом устроена именно так, и посторонний, знающий адрес, видит вежливый отказ вместо чужой базы клиентов.
Правило одной строкой: всё, что пришло из браузера, это пожелание. Фактом его делает сервер.
Туннель хорош для отладки и не годится дальше: адрес меняется, канал зависит от вашего ноутбука. Для постоянной работы нужен сервер с доменом и сертификатом. Самая дешёвая рабочая конфигурация складывается из обычного VPS за несколько долларов, домена и бесплатного сертификата. Про то, где вообще держать фронтенд и когда свой сервер не нужен, есть разбор вариантов хостинга.
Отдельная база на старте не нужна: sqlite лежит файлом рядом с кодом, копируется обычным cp и спокойно тянет сотни заказов в день. Момент переезда узнаётся по симптомам: несколько процессов на запись, заказы чаще одного в секунду, отчёты, которые считаются минутами. Куда переезжать и по каким признакам выбирать, разобрано в материале про выбор базы данных.
Свежая деталь про домен. С 20 июля 2026 Telegram запрещает вызывать свои методы с домена, отличного от домена самого приложения. Раньше можно было подтянуть внутрь чужой виджет или увести человека на соседний сайт, и системные кнопки продолжали работать. Теперь вызовы оттуда просто перестают отвечать. Отключить защиту можно там же, у BotFather, но тогда ответственность за чужие ссылки внутри приложения переходит к вам.
Тут развилка, и зависит она от того, что вы продаёте.
Цифровые товары и услуги внутри Telegram проводятся только через встроенную валюту платформы, Stars. Формулировка в документации жёсткая: платежи за цифровые товары должны идти исключительно в Stars, и наличие собственного сайта с эквайрингом этого не отменяет. Дело в правилах магазинов приложений, через которые Telegram распространяется. Криптовалюту вместо Stars принимать тоже нельзя, это отдельным пунктом.
Физические товары наоборот: они проводятся обычными платёжными инструментами, через подключённого провайдера. Кофе, доставка, запись к мастеру, всё это оплачивается картой, и Telegram комиссию не берёт.
Практический вывод для витрины кофейни или магазина: смотрите в сторону обычной оплаты картой. Для доступа к платному контенту, подписки внутри приложения или игровой валюты в сторону Stars. И если бот принимает платежи, у него должны быть команда /terms, работающий /paysupport и внятно объявленный способ связи с поддержкой: команда /support или что-то столь же очевидное.
Шаблон я собрал на нём же, статья на нём и проверялась. Это рабочий инструмент для переписки с клиентами, а демо на пять минут мы и сами насмотрелись.
Бот принимает сообщения, отвечает по правилам и копит базу: кто написал, когда, сколько сообщений, вся история диалога. Если правило не подошло и включена галочка, отвечает нейросеть с вашим промптом. Панель управления открывается внутри Telegram: клиенты, правила, промпт и рассылки с отложенным стартом.
Две вещи, которые превращают это из демонстрации в рабочий инструмент. Когда бот не смог ответить, владельцу сразу приходит сообщение: кто написал и что спросил. И ответить можно там же, из панели: открыл карточку клиента, прочитал переписку, написал ответ, он ушёл от имени бота.
База клиентов и переписка
Кто написал, когда пришёл, сколько сообщений. К контакту заметка, статус «не беспокоить» и вся история диалога.
Автоответы по правилам
Содержит, начинается с, точно совпадает. Первое подошедшее отвечает мгновенно и всегда одинаково.
Ответы нейросетью
Промпт задаётся в панели, историю переписки бот берёт из своей базы. Без ключа ветка выключена.
Рассылки с отложенным стартом
Очередь лежит в базе и переживает перезапуск. Заблокировавшие бота помечаются и больше не тревожатся.
Панель только для владельца
Вход по подписанному идентификатору. Проверка живёт в каждом обработчике, разметка тут ни при чём.
Ответ клиенту из панели
Прочитал переписку и ответил в том же экране. Сообщение уходит от имени бота и остаётся в истории.
82 теста
Подделка подписи, чужой в панели, порядок правил и модели, продолжение рассылки после падения.
И вещь, ради которой шаблон стоит открывать первой: режим предпросмотра. Панель поднимается в обычном браузере с поддельным владельцем, без бота и без туннеля. Интерфейс можно править и обновлять страницу, а Telegram подключать потом, когда экран уже нравится.
Правила, промпт и рассылки настраиваются в самой панели, код для этого трогать не нужно. Менять придётся только то, что захотите добавить сверх.
Бот с панелью управления на Python: aiogram 3, aiohttp, sqlite, правила, рассылки и режим предпросмотра. Бесплатно, нужен только вход.
Забрать шаблонСайт с админкой и личным кабинетом: что внутри и сколько стоит
Админкой пользуетесь вы, а кабинетом ваши клиенты. Разбираю, из чего собирается каждая половина и почему кабинет стоит от 17 тысяч рублей, а админка от 4.
Работает ли Gemini в Беларуси и что делать
В списке стран веб-версии Gemini больше 230 территорий, а Беларуси нет. При этом ИИ-ответы Google в поиске у вас работают легально. Разбираю почему.
Работает ли ChatGPT в Беларуси и что делать
Беларуси нет в списке стран OpenAI, а причину там не объясняют. Разбираю, где именно стоит ограничение, чем рискует аккаунт и что делать под вашу задачу.