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 и др.). Флоу:
- Frontend запрашивает
invoice_linkу вашего backend - Backend создаёт инвойс через Bot API
- Frontend открывает инвойс через
openInvoice() - Telegram обрабатывает платёж
- 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.

