Поделиться
Поделиться

Telegram Mini Apps — это веб-приложения, встроенные прямо в мессенджер. Они открываются без установки, работают в нативном контейнере Telegram и имеют доступ к данным пользователя, платёжной системе и API мессенджера. Аудитория Telegram в 2026 году превышает 1 миллиард пользователей — и это аудитория, которой не нужно объяснять, как установить приложение.

Архитектура Mini App

Telegram Client

    ├── Bot API (серверная сторона)
    │       └── Webhook → ваш backend

    └── Mini App WebView
            └── React SPA → ваш frontend

                    └── Telegram WebApp JS API

Mini App — это обычная веб-страница, доступная по HTTPS. Telegram загружает её в WebView и предоставляет глобальный объект window.Telegram.WebApp для взаимодействия с мессенджером.

Настройка проекта: React + Vite

npm create vite@latest my-mini-app -- --template react-ts
cd my-mini-app
npm install @tma.js/sdk @tma.js/react-sdk

Пакет @tma.js/sdk — это типизированная обёртка над нативным WebApp API. Настройка:

// main.tsx
import { SDKProvider } from '@tma.js/react-sdk'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <SDKProvider acceptCustomStyles debug>
      <App />
    </SDKProvider>
  </StrictMode>
)
// App.tsx
import { useLaunchParams, useMainButton, useBackButton } from '@tma.js/react-sdk'

export default function App() {
  const { initDataRaw, initData } = useLaunchParams()
  const mainButton = useMainButton()
  const backButton = useBackButton()

  useEffect(() => {
    mainButton.setParams({ text: 'Оформить заказ', isVisible: true })
    mainButton.on('click', handleCheckout)
    return () => mainButton.off('click', handleCheckout)
  }, [])

  return <div>{initData?.user?.firstName}, добро пожаловать!</div>
}

Валидация initData на backend

Это обязательный шаг безопасности. Telegram передаёт данные пользователя через initData — строку с HMAC-подписью. Без валидации любой может подделать запрос от имени другого пользователя.

# Python / FastAPI
import hashlib
import hmac
from urllib.parse import unquote, parse_qsl

def validate_init_data(init_data_raw: str, bot_token: str) -> dict:
    """
    Проверяет подпись initData от Telegram WebApp.
    Возвращает dict с данными пользователя или выбрасывает ValueError.
    """
    parsed = dict(parse_qsl(unquote(init_data_raw), strict_parsing=True))
    hash_value = parsed.pop('hash', None)

    if not hash_value:
        raise ValueError("hash отсутствует")

    # Формируем data-check-string
    data_check_string = '\n'.join(
        f"{k}={v}" for k, v in sorted(parsed.items())
    )

    # Вычисляем HMAC
    secret_key = hmac.new(b"WebAppData", bot_token.encode(), hashlib.sha256).digest()
    expected_hash = hmac.new(secret_key, data_check_string.encode(), hashlib.sha256).hexdigest()

    if not hmac.compare_digest(expected_hash, hash_value):
        raise ValueError("Подпись недействительна")

    return parsed


# Middleware для FastAPI
from fastapi import Request, HTTPException

async def verify_telegram_user(request: Request):
    init_data = request.headers.get("X-Telegram-Init-Data")
    if not init_data:
        raise HTTPException(status_code=401, detail="initData отсутствует")
    try:
        user_data = validate_init_data(init_data, settings.BOT_TOKEN)
        request.state.telegram_user = user_data
    except ValueError as e:
        raise HTTPException(status_code=403, detail=str(e))

На фронтенде передаём initDataRaw в заголовке каждого запроса:

// lib/api.ts
import { retrieveLaunchParams } from '@tma.js/sdk'

const { initDataRaw } = retrieveLaunchParams()

export const apiClient = axios.create({
  baseURL: import.meta.env.VITE_API_URL,
  headers: {
    'X-Telegram-Init-Data': initDataRaw
  }
})

Telegram Payments 2.0

Telegram позволяет принимать оплату прямо внутри Mini App через провайдеров (Stripe, ЮКassa, Robokassa и др.). Флоу:

  1. Frontend запрашивает invoice_link у вашего backend
  2. Backend создаёт инвойс через Bot API
  3. Frontend открывает инвойс через openInvoice()
  4. Telegram обрабатывает платёж
  5. Backend получает pre_checkout_query и successful_payment вебхуки
# Backend: создание инвойса
import httpx

async def create_invoice(user_id: int, product: Product) -> str:
    response = await httpx.post(
        f"https://api.telegram.org/bot{BOT_TOKEN}/createInvoiceLink",
        json={
            "title": product.name,
            "description": product.description,
            "payload": f"order:{product.id}:{user_id}",
            "provider_token": PAYMENT_PROVIDER_TOKEN,  # пусто для Telegram Stars
            "currency": "RUB",
            "prices": [{"label": product.name, "amount": product.price_kopecks}],
            "need_name": True,
            "need_phone_number": False,
            "need_email": True,
        }
    )
    return response.json()["result"]


# Обработка pre_checkout_query (обязателен ответ за 10 секунд)
@router.post("/webhook")
async def handle_webhook(update: dict):
    if "pre_checkout_query" in update:
        query = update["pre_checkout_query"]
        # Проверяем доступность товара
        is_available = await check_product_availability(query["invoice_payload"])
        await answer_pre_checkout_query(query["id"], ok=is_available)

    if "message" in update and "successful_payment" in update["message"]:
        payment = update["message"]["successful_payment"]
        await fulfill_order(payment["invoice_payload"], payment["telegram_payment_charge_id"])
// Frontend: открытие формы оплаты
import { useOpenLink } from '@tma.js/react-sdk'

function CheckoutButton({ product }) {
  const openLink = useOpenLink()

  const handlePay = async () => {
    const { invoiceLink } = await apiClient.post('/create-invoice', { productId: product.id })
    // Открываем форму оплаты в нативном интерфейсе Telegram
    Telegram.WebApp.openInvoice(invoiceLink, (status) => {
      if (status === 'paid') {
        showSuccessScreen()
      } else if (status === 'failed') {
        showErrorMessage()
      }
    })
  }

  return <MainButton onClick={handlePay} text="Оплатить" />
}

Haptic Feedback

Тактильная отдача делает Mini App похожей на нативное приложение. Используйте её осознанно:

const haptic = useHapticFeedback()

// При нажатии кнопки
haptic.impactOccurred('medium')

// При успешном действии
haptic.notificationOccurred('success')

// При ошибке
haptic.notificationOccurred('error')

// При выборе элемента в списке
haptic.selectionChanged()

Тематизация

Mini App автоматически наследует тему Telegram. Используйте CSS-переменные, которые Telegram подставляет в :root:

/* styles/theme.css */
:root {
  --tg-theme-bg-color: #ffffff;
  --tg-theme-text-color: #000000;
  --tg-theme-hint-color: #999999;
  --tg-theme-link-color: #2481cc;
  --tg-theme-button-color: #2481cc;
  --tg-theme-button-text-color: #ffffff;
  --tg-theme-secondary-bg-color: #f1f1f1;
}

.card {
  background: var(--tg-theme-secondary-bg-color);
  color: var(--tg-theme-text-color);
}

.primary-button {
  background: var(--tg-theme-button-color);
  color: var(--tg-theme-button-text-color);
}

В JavaScript цвета доступны через:

const { colorScheme, themeParams } = useMiniApp()
// colorScheme: 'dark' | 'light'
// themeParams.bgColor, themeParams.textColor, ...

Деплой

Mini App — это статический сайт + backend. Для фронтенда подойдёт любой CDN или VPS с Nginx:

server {
  listen 443 ssl;
  server_name mini-app.example.com;

  ssl_certificate /etc/letsencrypt/live/mini-app.example.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/mini-app.example.com/privkey.pem;

  root /var/www/mini-app/dist;
  index index.html;

  # SPA fallback
  location / {
    try_files $uri $uri/ /index.html;
  }

  # Кешируем статику
  location ~* \.(js|css|png|jpg|svg|woff2)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
  }
}

Обязателен HTTPS — Telegram не загрузит Mini App по HTTP.

Регистрация в BotFather:

/newapp → выбираем бота → вводим URL → получаем ссылку t.me/your_bot/appname

Итог

Telegram Mini Apps — зрелая платформа с богатым API: платежи, геолокация, сканер QR-кодов, файлы, биометрия. Главное отличие от обычного PWA — встроенная аудитория и нативное ощущение внутри мессенджера.

Инфраструктуру для backend-части Mini App удобно разворачивать через Docker и GitLab CI — подробности в статье GitLab CI/CD: настройка pipeline.