Что такое Mini App (Web App) в TG

Mini App (он же Web App) — это веб-приложение (проще говоря сайт), встроенное в интерфейс Telegram. Оно открывается прямо внутри чата: пользователь нажимает кнопку «Открыть» и видит полноценную веб-страницу с возможностью взаимодействия, стилизацией под тему Telegram и передачей данных обратно в бота.

Это даёт нам множество преимуществ, таких как:

  • работа с авторизованными пользователями (tg.initDataUnsafe.user);

  • визуальное соответствие Telegram (тёмная/светлая тема);

  • доступ к данным юзера (first_name, id и др.) без дополнительных анкет;

  • кнопки и взаимодействие с ботом без лишних сообщений.

В прошлой статье я уже рассмотрел основные возможности Web App, а сегодня же подведём итоги, расскажем все особенности и нюансы работы с Web App в TG и соберём простенькое веб-приложение.

Особенности и нюансы работы с Web App

Подключение и запуск

Здесь важно знать несколько вещей:

  1. tg.initDataUnsafe — метод для получения всех данных пользователя. Работает только тогда, когда Mini App открыт из Telegram, а напрямую из браузера. Тем самым мы можем проверять, не открыл ли его пользователь с внешнего браузера, чтобы не словить кучу багов;

  2. tg.sendData() — метод для отправки данных обратно в бота. Работает только, если MiniApp открыт через reply-кнопку с web_app. То есть при открытии через inline-кнопку sendData() не сработает.

Стилизация темы под Telegram

Telegram API предоставляет переменные для получения пользовательской темы (той темы, которую пользователь выставил в своём клиенте телеграм), что позволяет нам стилизовать страницу под тему пользователя.

Сами переменные:

  • --tg-theme-bg-color — буквально цвет фона;

  • --tg-theme-text-color — цвет шрифта соответственно;

  • --tg-color-scheme — получение темы клиента (light или dark);

  • --tg-theme-hint-color — вторичный текст / подсказки;

  • --tg-theme-link-color — цвет ссылок;

  • --tg-theme-button-color — цвет кнопки;

  • --tg-theme-button-text-color — текст на кнопке;

  • --tg-theme-secondary-bg-color — вторичный цвет фона;

  • --tg-theme-header-bg-color — фон заголовка (хеадера);

  • и т. д. Все переменные описаны здесь.

Безопасность и идентификация

  • initData — зашифрованная строка, которая верифицируется сервером (углубимся чуть позже);

  • initDataUnsafe — уже распарсенный объект. Не рекомендуется использовать на проде.

Нюансы деплоя

  • Web App обязательно должен быть доступен по https.Обычный http не подойдёт. Однако в нашем случае это не проблема. Amvera предоставляет совершенно бесплатный домен и SSL-сертификат, который вы сможете активировать для своего проекта прям в ЛК за пару минут.

  • Telegram отправляет данные только после клика по web_app-кнопке, при открытии ссылки данные не отправятся.

Пример initDataUnsafe и initData

Когда Mini App открыт корректно, Telegram передаёт два вида данных:

initDataUnsafe — уже распарсенный объект:

{
  "user": {
    "id": ,
    "first_name": "",
    "last_name": "",
    "username": "",
    "language_code": "",
    "is_premium": true,
    "allows_write_to_pm": true,
    "photo_url": ""
  },
  "chat_instance": "",
  "chat_type": "sender",
  "auth_date": "",
  "signature": "",
  "hash": ""
}

Этот объект доступен в tg.initDataUnsafe и удобен для вывода имени, аватарки и др., но небезопасен. Злоумышленник может подделать его при открытии в браузере.

initData — строка, которую нужно проверить на сервере:

user=%7B%22id%22%3A<...>%2C%22first_name%22%3A%22<...>%22%7D&chat_instance=&chat_type=sender&auth_date=&signature=&hash=

Это URL-кодированная строка, которую Telegram передаёт в tg.initData. Ее нужно обязательно верифицировать на сервере, чтобы убедиться, что данные подлинные, и пользователь действительно открыл Web App через Telegram.

Обработка данных пользователя

Когда мы успешно проверили initData на сервере (то есть убедились, что всё пришло именно от Telegram, а не подделано), можем спокойно доставать нужные данные о пользователе.

Внутри параметра user содержится полезная информация:

  • id — уникальный Telegram айди пользователя (нужен, чтобы идентифицировать его в базе или писать в личку);

  • first_name — его имя (может быть любое, хоть смайлик);

  • last_name — фамилия, но часто бывает просто пустой;

  • username — ник, если он есть (то самое @имя);

  • language_code — язык пользователя, например ru, en и т.п.

  • is_premium — флаг, есть ли у человека Telegram Premium;

  • allows_write_to_pm — можно ли боту написать этому человеку напрямую;

  • photo_url — ссылка на аватарку в Telegram, можно вставить как обычную картинку.

Получить, например, first_name можно так:

  • const firstName = tg.initDataUnsafe.user.first_name;

Теперь, когда мы разобрались с подводными камнями, давай соберём простой Mini App с кнопкой и формой и свяжем его с ботом.

Практическая часть: разработка Web App

Как всегда, начнём с малого: проверим, запущен ли Web App из Telegram, и если нет, отправим предупреждение.

Для этого напишем простенький index.html:

   <title>Mini App</title>
  <style>
    :root {
      --bg-color: var(--tg-theme-bg-color, #ffffff);
      --text-color: var(--tg-theme-text-color, #000000);
      --accent-color: var(--tg-theme-button-color, #0088cc);
    }

    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background: var(--bg-color);
      color: var(--text-color);
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      text-align: center;
    }

    .warn {
      background: var(--bg-color);
      color: var(--text-color);
      padding: 40px 20px;
      display: none;
      height: 100vh;
      width: 100vw;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }

    .warn img {
      width: 120px;
      height: 120px;
      margin-bottom: 20px;
      animation: float 2s ease-in-out infinite;
    }

    @keyframes float {
      0% { transform: translateY(0px); }
      50% { transform: translateY(-10px); }
      100% { transform: translateY(0px); }
    }

    .warn p {
      font-size: 18px;
      margin: 8px 0;
    }

    .main {
      display: none;
      padding: 20px;
    }

    .btn {
      padding: 12px 24px;
      font-size: 16px;
      border: none;
      border-radius: 5px;
      background: var(--accent-color);
      color: var(--tg-theme-button-text-color, #ffffff);
      cursor: pointer;
      margin-top: 20px;
    }
  </style>


  <div id="notInTelegram" class="warn">
    <img alt="TLogo" src="https://telegram.org/img/t_logo.png">
    <p><strong>Упс!</strong></p>
    <p>Это приложение должно быть запущено <strong>внутри Telegram!</strong></p>
  </div>

  <div id="mainApp" class="main">
    <h1>Привет из Mini App </h1>
    <p>MiniApp успешно открыт в Telegram!</p>
    <button id="actionBtn" class="btn">Продолжить</button>
  </div>

Такой шаблон можно использовать в каждом Mini App, чтобы исключить ошибки при открытии через браузер. Telegram не передаёт данные, если Web App не был открыт по кнопке. Мы сразу покажем об этом сообщение.

Что здесь важно:

  • !tg || !tg.initDataUnsafe?.user — проверка, что Mini App открыт не в Telegram (например, просто в браузере);

  • tg.expand() — разворачиваем приложение на весь экран;

  • Стили и цвета берутся напрямую из Telegram-клиента, без собственной темы.

Открытие Telegram Web App в браузере
Открытие Telegram Web App в браузере

Отправка данных из Mini App: sendData() и как он (не) работает

Мини-приложения могут отправлять данные обратно в бота. Делается это через метод:

tg.sendData("любая строка");

Обычно это строка в формате JSON. Бот получает её в событии WEB_APP_DATA.

Но! Есть важное ограничение:

tg.sendData() работает ТОЛЬКО, если Mini App открыт по кнопке с параметром web_app, например:

KeyboardButton(text="Открыть", web_app=WebAppInfo(url="https://..."))

Если открыть Web App по inline-кнопке, sendData() работать не будет

Практическая часть: разработка Web App

Как всегда начнём с малого: проверим, запущен ли Web App из Telegram, и если нет - отправим предупреждение.

Для этого напишем простенький index.html:

   <title>Mini App</title>
  <style>
    :root {
      --bg-color: var(--tg-theme-bg-color, #ffffff);
      --text-color: var(--tg-theme-text-color, #000000);
      --accent-color: var(--tg-theme-button-color, #0088cc);
    }

    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background: var(--bg-color);
      color: var(--text-color);
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      text-align: center;
    }

    .warn {
      background: var(--bg-color);
      color: var(--text-color);
      padding: 40px 20px;
      display: none;
      height: 100vh;
      width: 100vw;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }

    .warn img {
      width: 120px;
      height: 120px;
      margin-bottom: 20px;
      animation: float 2s ease-in-out infinite;
    }

    @keyframes float {
      0% { transform: translateY(0px); }
      50% { transform: translateY(-10px); }
      100% { transform: translateY(0px); }
    }

    .warn p {
      font-size: 18px;
      margin: 8px 0;
    }

    .main {
      display: none;
      padding: 20px;
    }

    .btn {
      padding: 12px 24px;
      font-size: 16px;
      border: none;
      border-radius: 5px;
      background: var(--accent-color);
      color: var(--tg-theme-button-text-color, #ffffff);
      cursor: pointer;
      margin-top: 20px;
    }
  </style>


  <div id="notInTelegram" class="warn">
    <img alt="TLogo" src="https://telegram.org/img/t_logo.png">
    <p><strong>Упс!</strong></p>
    <p>Это приложение должно быть запущено <strong>внутри Telegram!</strong></p>
  </div>

  <div id="mainApp" class="main">
    <h1>Привет из Mini App ?</h1>
    <p>MiniApp успешно открыт в Telegram!</p>
    <button id="actionBtn" class="btn">Продолжить</button>
  </div>

Такой шаблон можно использовать в каждом Mini App, чтобы исключить ошибки при открытии через браузер. Telegram не передаёт данные, если Web App не был открыт по кнопке. Мы сразу покажем об этом сообщение.

Что тут важно:

  • !tg || !tg.initDataUnsafe?.user — проверка, что Mini App открыт не в Telegram (например, просто в браузере).

  • tg.expand() — разворачиваем приложение на весь экран.

  • Стили и цвета берутся напрямую из Telegram-клиента, без собственной темы.

Открытие Telegram Web App в браузере
Открытие Telegram Web App в браузере

Отправка данных из Mini App: sendData() и как он (не) работает

Мини-приложения могут отправлять данные обратно в бота. Делается это через метод:

tg.sendData("любая строка");

Обычно это строка в формате JSON. Бот получает её в событии WEB_APP_DATA.

Но! Есть важное ограничение:

tg.sendData() работает ТОЛЬКО, если Mini App открыт по кнопке с параметром web_app, например:

KeyboardButton(text="Открыть", web_app=WebAppInfo(url="https://..."))

Если открыть Web App по inline-кнопке, sendData() работать не будет — Telegram просто проигнорирует вызов. Это одна из главных "ловушек" Mini App.

Создадим простую форму в HTML, где пользователь может ввести случайные данные

Например: имя и сообщение. Такая реализация уже практична для отправки отзывов прям администратору через приятный интерфейс MiniApp. Единственное, что понадобится — правильно обработать данные самим ботом.

  <div id="mainApp" class="main">
    <h1>Привет из Mini App </h1>
    <p>MiniApp успешно открыт в Telegram!</p>
    <button id="actionBtn" class="btn">Открыть форму</button>

    <form id="dataForm">
      <input required="" id="nameInput" placeholder="Ваше имя" type="text">
      <textarea required="" id="msgInput" placeholder="Ваше сообщение"></textarea>
      <button class="btn" type="submit">Отправить</button>
    </form>
  </div>
    .btn {
      padding: 12px 24px;
      font-size: 16px;
      border: none;
      border-radius: 8px;
      background: var(--accent-color);
      color: var(--button-text-color);
      cursor: pointer;
      transition: background 0.2s ease;
    }

    .btn:hover {
      opacity: 0.9;
    }

    form {
      margin-top: 20px;
      width: 100%;
      display: none;
      flex-direction: column;
      align-items: center;
      gap: 12px;
    }

    input, textarea {
      width: 100%;
      padding: 12px;
      font-size: 15px;
      border: 1px solid var(--border-color);
      border-radius: 6px;
      background: var(--input-bg);
      color: var(--text-color);
      resize: vertical;
    }

    textarea {
      min-height: 100px;
    }

и скрипт:

    const form = document.getElementById("dataForm");
    const actionBtn = document.getElementById("actionBtn");

    actionBtn.addEventListener("click", () =&gt; {
      form.style.display = "flex";
      actionBtn.style.display = "none";
    });

    form.addEventListener("submit", (e) =&gt; {
      e.preventDefault();

      const name = document.getElementById("nameInput").value.trim();
      const msg = document.getElementById("msgInput").value.trim();

      if (!name || !msg) return;

      const payload = {
        name,
        message: msg
      };

      tg.sendData(JSON.stringify(payload));
    });


Бот должен слушать события WEB_APP_DATA, и если пользователь отправил данные — обработать их.

Вот пример на aiogram 3:

@dp.message(F.content_type == ContentType.WEB_APP_DATA)
async def handle_webapp_data(message: types.Message):
    try:
        data = json.loads(message.web_app_data.data)
        name = data.get("name", "Неизвестный пользователь")
        msg = data.get("message", "(пусто)")

        await message.answer(
            f"<b>{name}</b> отправил(а) сообщение:\n\n{msg}",
            parse_mode=ParseMode.HTML
        )

    except Exception as e:
        await message.answer("Произошла ошибка при разборе данных из Mini App")

Что важно учитывать:

  • sendData() = связь Mini App и бота;

  • работает только с reply-кнопкой;

  • inline-кнопки НЕ отправляют sendData(). Нужно открывать именно через reply;

  • данные можно передавать только как строку (обычно JSON).

Итог по отправке данных

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

  • Проверять, открыто ли веб-приложение через Telegram

  • Отправлять данные через tg.sendData()

Демонстрация работы Telegram Web App
Демонстрация работы Telegram Web App
Отправка данных
Отправка данных

Код бота

Бот написан на aiogram v3, слушает все сообщения и в ответ на них присылает web_app в KeyboardButton, получает данные от WEB_APP и обрабатывает их:

import asyncio
import os
import json

from aiogram import Bot, Dispatcher, types, F
from aiogram.enums import ParseMode, ContentType
from aiogram.types import WebAppInfo, KeyboardButton, ReplyKeyboardMarkup
from dotenv import load_dotenv

load_dotenv()

bot = Bot(os.getenv("BOT_TOKEN"))
dp = Dispatcher()

@dp.message()
async def show_miniapp(message: types.Message):
    keyboard = ReplyKeyboardMarkup(
        keyboard=[
            [KeyboardButton(
                text="Открыть MiniApp",
                ##### Заменить ниже:
                web_app=WebAppInfo(url="https://ССЫЛКА-КОТОРУЮ-ВЫ-ПОЛУЧИТЕ-ПОЗЖЕ/")
            )]
        ],
        resize_keyboard=True
    )
    await message.answer("Нажмите кнопку ниже, чтобы открыть Mini App:", reply_markup=keyboard)


@dp.message(F.content_type == ContentType.WEB_APP_DATA)
async def handle_webapp_data(message: types.Message):
    try:
        data = json.loads(message.web_app_data.data)
        name = data.get("name", "Неизвестный пользователь")
        msg = data.get("message", "(пусто)")

        await message.answer(
            f"<b>{name}</b> отправил(а) сообщение:\n\n{msg}",
            parse_mode=ParseMode.HTML
        )

    except Exception as e:
        await message.answer("Произошла ошибка при разборе данных из Mini App")

async def main():
    await bot.delete_webhook(drop_pending_updates=True)
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

> В .env нужно добавить строку:
> &gt; BOT_TOKEN=ВАШ:ТОКЕН &gt;

Деплой

Обратите внимание: мы написали минимальную реализацию Mini App, где не проверяется initData. Это важно, ведь любой злоумышленник может подменить данные.

После того как мы подготовили нашу простенькую реализацию Web App, мы можем перейти к деплою. Разворачиваться мы будем в Amvera.

Amvera - это сервис для простого и быстрого деплоя IT-приложений, особенно удобный для тех, кто не хочет тратить время на настройку серверов. После регистрации вы сразу получаете 111 рублей на баланс, которых хватит, чтобы без лишних вложений развернуть первое приложение.

А ещё — бесплатный домен с SSL-сертификатом, так что вы сразу получите безопасную ссылку вида https://имя-проект.amvera.io, без танцев с DNS и сертификатами.

Итак, для начала нам нужно зарегистрироваться в сервисе по ссылке и подтвердить почту. Сразу после этого вы получите 111 рублей на баланс.

На странице проектов жмём кнопку "Создать проект" и создаем со следующими параметрами:

  • Тип сервиса: Приложение. Жмём далее;

  • Название проекта: любое;

  • Тариф: рекомендуется выбрать тариф "Начальный" или выше. Жмём далее.

Все остальные этапы создания можно пропускать. Мы настроим все в настройках проекта.

Загрузка файлов

Чтобы развернуть сайт, нам понадобится загрузить простой Python-код, который запустит Flask-сервер и отрендерит наш index.html.

main.py:

from flask import Flask, render_template  
  
app = Flask(__name__, template_folder='.')  
  
@app.route("/")  
def web():  
    return render_template('index.html')  
  
if __name__ == "__main__":  
    app.run(debug=True, host="0.0.0.0", port='80')  

Код взят из одной из старших статей.

Вместе с ним нам нужно загрузить файл requirements.txt. Файл обязательно нужен для установки Flask==3.0.3

requirements.txt:

Flask==3.0.3

Грузим все 3 файла:

  • main.py

  • requirements.txt

  • index.html с Mini App в

Позже здесь будет и amvera.yml - файл конфигурации
Позже здесь будет и amvera.yml - файл конфигурации

Создание конфигурации

Предпоследний этап — создание конфигурации. Всё, что вам нужно — повторить конфигурацию со скриншота во вкладке "Конфигурация":

Задание конфигурации
Задание конфигурации

Жмём «Применить» и «Собрать». Сборка займет около пяти минут, запуск около минуты.

Создание бесплатного домена с SSL

Пока проект собирается, создадим домен в одноимённой вкладке.

Жмём кнопку «Создать доменное имя», выбираем:

  • Тип подключения: HTTPS

  • Тип домена: Бесплатный домен Amvera

Применяем, и через тридцать секунд домен будет готов.

Результат

После действий выше, ваше мини приложение будет готово и останется лишь прописать полученную ссылку в код main.py и запустить бота.

Развернуть бота можно будет по этой идентичной логике, связав по внутреннему доменному имени.


Как вам статья? Напишите в комментариях, что ещё хотелось бы разобрать. Возможно, в следующий раз добавим примеры с базой данных, оплатами или более сложным интерфейсом и бэкендом.

Релевантные или упомянутые ресурсы:

Комментарии (1)


  1. NikitinIlya
    10.08.2025 13:40

    Было бы здорово рассмотреть подключение оплаты в Telegram Web App. Там много сложностей и подводных камней, а функционал часто нужен в таком типе приложений.