Всем привет! В этой статье я собрал практические фишки работы с Telegram Mini App (Web App): что здесь реально проще, чем в обычном вебе, какие возможности Telegram дает из коробки, и что чаще всего вызывает затруднения при разработке. Помимо этого, я покажу, как можно быстро развернуть простой MiniApp с проверкой initData буквально за 5 минут в движке приложений Amvera Cloud.
Что такое Telegram Mini App
Если вдруг кто-то не в курсе, начнем с малого - что вообще такое Mini App?
Итак, Telegram Mini App (или же Telegram Web App) - это веб-приложение, которое открывается внутри Telegram (через встроенный браузер/WebView) и получает некие нативные возможности через подключенный JS-API telegram-web-app.js в коде страницы. После подключения окружения выше становится доступен объект window.Telegram.WebApp, с помощью которого и происходит вообще все общение с переданными данными из Telegram.
MiniApp можно запускать разными способами: из кнопки под сообщением, из меню бота, из специальной кнопки "Main Mini App", которую можно настроить в BotFather и которая будет доступна даже в ленте чатов, из inline-режима, по прямой ссылке и т.д. И все это важно, так как контекст запуска напрямую влияет на доступные сценарии (например, отправить некоторые данные из страницы боту можно только через ReplyKeyboardMarkup).
Все это к тому, что Web App здесь - это не простой сайт, а сайт с интегрированными возможностями Telegram, которые дают:
пользователя и контекст;
нативные кнопки (Bottom/Main, Back, Settings);
тему/цвета для конкретного пользователя, который открыл MiniApp;
события;
хранилище и др.
Фишки работы через Mini App
Ниже - самые явные плюсы, которые показывают преимущества Mini App.
1. Низкий порог вхождения для пользователей
Вы не заставляете пользователя устанавливать приложение через AppStore/Google Play, искать сайт в поисковике и проходить авторизацию/регистрацию.
Все это не нужно, так как у вас уже есть точка входа: бот, кнопка, прямые ссылки с startapp. А почему не нужна регистрация - ниже.
2. Авторизация без логина
Как бы было хорошо, если бы не пришлось создавать формы входа/регистрации, все это закреплять какой-никакой защитой и страдать с сессиями, верно? А все это уже возможно при работе через MiniApp и является, наверное, одним из главных его плюсов.
В чем суть: при открытии страницы пользователем, Telegram передает данные и подпись (initData). И обработав это, вы получаете все необходимые данные пользователя.
Единственное и самое важное - подпись обязательно нужно валидировать на бэкенде, иначе любой сможет подставить любые данные из WebApp. Как это делать, я также распишу чуть ниже.
3. Геолокация, датчики и биометрия
Здесь тоже очень интересно: Telegram умеет давать доступ к возможностям устройства, которые в обычном вебе часто упираются в разрешения.
Геолокация (LocationManager)
Координаты пользователя можно запросить прямо из MiniApp. Для этого можно найти широкое применение: в доставке, картах и т.п.
const tg = window.Telegram?.WebApp;
const loc = tg?.LocationManager;
// Обязательно! Инициализация менеджера
loc?.init(() => {
if (!loc.isLocationAvailable) return;
loc.getLocation((pos) => {
if (!pos) {
// Доступ не выдан или недоступно на устройстве
return;
}
console.log("геопозиция:", pos.latitude, pos.longitude);
});
});
// openSettings() можно вызывать только в ответ на какое-то действие пользователя (клик например)
document.getElementById("geoSettingsBtn")?.addEventListener("click", () => {
tg?.LocationManager?.openSettings();
});
Датчики (Акселерометр, гироскоп, ориентация экрана)
Telegraem дает готовые объекты для каждого датчика:
Telegram.WebApp.AccelerometerTelegram.WebApp.GyroscopeTelegram.WebApp.DeviceOrientation
Для работы с каждым из объектов нужно:
Проверить, что объект существует (если нет - нет разрешения или не поддерживается устройством)
Подписаться на изменения с помощью
WebApp.onEvent()Запустить через
start{{ refresh_rate: }}Читать значения
Акселерометр
Что такое акселерометр? Акселерометр (G-сенсор) — это специальный датчик, который позволяет с помощью определения ускорения устройства по осям xyz узнавать угол наклона электронного устройства по отношению к земной поверхности. На основании этих данных программное обеспечение смартфона идентифицирует положение смартфона в пространстве. Источник
Подробнее из документации Telegram: https://core.telegram.org/bots/webapps#accelerometer
const tg = window.Telegram?.WebApp;
tg?.ready?.();
// Получаем объект
const accel = tg?.Accelerometer;
// Из пунктов выше: проверяем наличие
if (accel) {
const onAccel = () => {
// Логируем изменения
console.log("accel:", accel.x, accel.y, accel.z);
};
// Подписываемся на событие изменения значений
tg.onEvent("accelerometerChanged", onAccel);
// И подписываемся на обновления
// refresh_rate в миллисекундах
accel.start({ refresh_rate: 100 }, (started) => {
if (!started) {
tg.offEvent("accelerometerChanged", onAccel);
}
});
// И если датчик больше не нужен:
// accel.stop();
// tg.offEvent("accelerometerChanged", onAccel);
}
Гироскоп
Почти то же самое, что акселерометр, только он определяет не то, как устройство вращается, а с какой скоростью оно вращается.
Подробнее: https://core.telegram.org/bots/webapps#gyroscope
const tg = window.Telegram?.WebApp;
tg?.ready?.();
// Получаем объект
const gyro = tg?.Gyroscope;
// Ровно как с акселерометром проверяем наличие объекта
if (gyro) {
const onGyro = () => {
// rotation rate
console.log("gyro:", gyro.x, gyro.y, gyro.z);
};
// Подписка на изменения
tg.onEvent("gyroscopeChanged", onGyro);
// Запускаем
gyro.start({ refresh_rate: 100 }, (started) => {
if (!started) {
tg.offEvent("gyroscopeChanged", onGyro);
}
});
// остановка:
// gyro.stop();
// tg.offEvent("gyroscopeChanged", onGyro);
}
Ориентация устройства
С этим, наверное, знакомы все. Просто определяет поворот экрана в пространстве.
const tg = window.Telegram?.WebApp;
tg?.ready?.();
// Получаем объект
const orient = tg?.DeviceOrientation;
// Проверяем его наличие
if (orient) {
const onOrient = () => {
// alpha/beta/gamma — в радианах, absolute — абсолютные ли значения
console.log("orientation:", orient.alpha, orient.beta, orient.gamma, "abs:", orient.absolute);
};
// Подписка на изменения
tg.onEvent("deviceOrientationChanged", onOrient);
orient.start({ refresh_rate: 100, need_absolute: false }, (started) => {
if (!started) {
tg.offEvent("deviceOrientationChanged", onOrient);
}
});
// остановка:
// orient.stop();
// tg.offEvent("deviceOrientationChanged", onOrient);
}
Подробнее: https://core.telegram.org/bots/webapps#deviceorientation
Биометрия
MiniApp умеет показывать нативное окно FaceID/TouchID (или отпечаток/лицо) и возвращать токен биометрии, который можно хранить в зищащенном хранилище устройства и использовать далее.
Как это работает:
Проверяем, есть ли на устройстве биометрия.
Запускаем проверку биометрии.
Если все прошло успешно - Telegram возвращает biometric_token, который мы уже может сохранить в хранилище клиента Telegram и использовать вдальнейшем.
const tg = window.Telegram?.WebApp;
const bm = tg?.BiometricManager;
const btn = document.getElementById("bioBtn");
function initBiometrics() {
if (!bm || !btn) return;
bm.init(() => {
if (!bm.isBiometricAvailable) {
btn.textContent = "Биометрия недоступна на вашем устройстве";
btn.disabled = true;
}
});
}
btn?.addEventListener("click", () => {
if (!bm || !bm.isBiometricAvailable) return;
bm.requestAccess({ reason: "Нужно подтвердить действие" }, (granted) => {
if (!granted) return;
bm.authenticate({ reason: "Подтвердите через биометрию" }, (ok, token) => {
if (!ok || !token) return;
bm.updateBiometricToken(token, (updated) => {
if (!updated) return;
// это уже отправляем на бекенд, опционально. Вы можете выполнять любой код.
fetch("/api/bio/confirm", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
token,
device_id: bm.deviceId
})
});
});
});
});
});
initBiometrics();
Чуть понятнее про методы:
init(cb)- подготавливает менеджер и проверяет, доступно ли на устройстве ли поддержка.requestAccess({ reason }, cb)- просит у пользователя разрешение на биометрию в MiniApp.authenticate({ reason }, cb)- показывает окно FaceID/TouchID и возвращает результат +token(если все успешно, конено).
После успешной аутентификации вы получаете токен и по желанию сохраняете/обновляете его через
updateBiometricToken(). Telegram же хранит его в защищенном хранилище. Факт сохранения проверяете черезisBiometricTokenSaved.
Подробнее: https://core.telegram.org/bots/webapps#biometricmanager
3. Нативный UX и темы пользователя
Множество кнопок, popup'ы, цвета темы пользователей - все это также предоставляем MiniApp.
Хотел бы немного подробнее разжевать про темы пользователя.
Telegram сам знает, что у человека включено: светлая или темная тема, какие цвета у интерфейса, какой фон у карточек, какой цвет у текста и ссылок. Простым словом: знает тему пользователя. И при открытии Mini App он отдает это в виде themeParams, плюс пробрасывает набор CSS-переменных, чтобы вы могли быстро подстроить интерфейс под уникальный стиль пользователя. То есть если у пользователя темная тема, то и в MiniApp будет темная тема; если у пользователя условный button_text_color (см. картинку ниже) розовый, то и в вашем MiniApp он будет розовый, если будете использовать переданные переменные.

4. Платежи и монетизация
MiniApp умеет открывать интерфейс openInvoice, который можно создать со стороны BotAPI (createInvoiceLink).
5. Еще 8 мелких фишек, которые почти всегда пригождаются
1. BottomButton (ранее MainButton)
Если у вас есть какое-то действие по типу "Сохранить", "Отправить", "Подтвердить", то можно использовать нативную кнопку BottomButton:
const tg = window.Telegram?.WebApp;
function updateBottomButton(canSubmit) {
if (!tg?.BottomButton) return;
// tg.BottomButton.setText() - устанавливаем текст кнопки (как ни странно)
tg.BottomButton.setText("Сохранить");
// tg.BottomButton.show() - показываем кнопку
tg.BottomButton.show();
// tg.BottomButton.enable() - делаем кнопку активной
// с disable() - делаем кнопку неактивной
if (canSubmit) tg.BottomButton.enable();
else tg.BottomButton.disable();
}
tg?.BottomButton?.onClick(() => {
// Анимируем кнопку, будто что-то происходит (показываем прогресс)
tg.BottomButton .showProgress();
// что делаем, если кнопка нажата
});
Подробнее можно почитать здесь: https://core.telegram.org/bots/webapps#bottombutton
2. BackButton
Почти как с MainButton. Если у вас в Mini App есть несколько экранов, то на втором экране можно включить BackButton.
Покажу на примере список - карточка
const tg = window.Telegram?.WebApp;
function showScreen(name) {
if (name === "list") tg?.BackButton?.hide();
// Если это карточка - показываем BackButton
else tg?.BackButton?.show();
}
tg?.BackButton?.onClick(() => {
// При нажатии на BackButton просто возвращаем экран списка
showScreen("list");
});
3. Закрытие MiniApp
Когда все сделано и нужно закрыть WebApp, можно воспользоваться следующим методом:
window.Telegram?.WebApp?.close();4. Ссылки: openLink и openTelegramLink
Рекомендуется не использовать тег <a> для гиберссылок. В Telegram для этого существуют специальные методы:
Обычные внешние ссылки - открывать через
openLink,Ссылки на телеграм (начинаются на t.me/...) - через
openTelegramLink
const tg = window.Telegram?.WebApp;
// любая внешняя ссылка
document.getElementById("siteLink").addEventListener("click", (e) => {
e.preventDefault();
tg?.openLink("https://habr.com");
});
// тг ссылки
document.getElementById("tgLink").addEventListener("click", (e) => {
e.preventDefault();
tg?.openTelegramLink("https://t.me/botfather");
});
5. expand() - открытие MiniApp на максимальную доступную высоту
Если у вас используется какая-то длинная форма, то элементы могут тесниться. Обычно для решения этой проблемы хватает развертывание Mini App на всю высоту:
window.Telegram?.WebApp?.expand();6. Вибрация
Haptic feedback - вибрация внутри MiniApp.
Можно использовать:
"success"если все удалось (при сохранении, например);"error"после ошибки;"warning"при предупреждении.
const haptic = window.Telegram?.WebApp?.HapticFeedback;
haptic?.notificationOccurred("success"); // success / error / warning
7. CloudStorage
Используется для того, чтобы запомнить какую-то мелочь по типу "скрыть подсказки", "выбранный режим отображения" и т.п. Тут важно: это не база данных, сюда кладут только мелочи.
Подробнее: https://core.telegram.org/bots/webapps#cloudstorage
8. Подтверждение закрытия
Если у вас есть какая-то важная форма и пользователь может случайно закрыть WebApp - включайте защиту от закрытия:
window.Telegram?.WebApp?.enableClosingConfirmation();
// или, для отключения:
window.Telegram?.WebApp?.disableClosingConfirmation();
Подводные камни и как не облажаться с Web App
Безопасность и авторизация
Обязательно нужно проверять initData. Его всегда можно подменить со стороны клиента.
Еще раз, initData ВСЕГДА нужно валидировать на бэкенде по специальному алгоритму. При валидации подпись (hash) проверяется с использованием токена бота, по этой причине валидацию никогда и ни в коем случае нельзя делать через фронт.
Также рекомендуется давать TTL по auth_date на стороне бэкенда, иначе украденный или кривой initData может жить бесконечно. Это желательно, однако в практической части статьи я покажу минимальный вариант без TTL.
Dev-режим и тестирование
Для разработки Mini App Dev-режим использовать довольно сложно. Лично я сталкивался с невозможностью нормальной разработки без деплоя, из-за чего приходилось подставлять фейковые значения, что не очень хорошо для дальнейшего прода - можно пропустить что-то важное.
Передача данных боту через sendData()
Если вы хотите передать некоторые данные со страницы Mini App боту, в котором этот самый MiniApp открыт (например имя, которое ввел пользователь внутри Mini App), то передача будет работать только если Mini App запущен из кнопки в клавиатуре (ReplyKeyboardMarkup с web_app). Из любой другой кнопки передать данные таким образом не получится. Однако ничего вас не ограничивает в обмене через ваш бекенд.
Сравнение с обычным вебом
Web App похож по своей сути на классический веб-сайт, но есть и отличия:
Обязательность адаптива
Telegram Mini App: Адаптив сильно проще: в первую очередь это mobile first
Обычный веб: Нужно делать мобильный адаптив и адаптив под широкие экраны
Как пользователь открывает страницу
Telegram Mini App: Из Telegram: бот, кнопка, ссылка
Обычный веб: Через браузер: сайт/ссылка или как приложение из маркета приложений
Вход без логина
Telegram Mini App: Можно взять пользователя из
initDataОбычный веб: Обычно, нужна регистрация (если она вообще нужна в пронекте)
Кнопки и интерфейс
Telegram Mini App: Есть нативные кнопки Telegram (Main/Back/Settings)
Обычный веб: Все делаете сами на странице
Тема и цвета
Telegram Mini App: Telegram дает
themeParamsи CSS переменные пользователяОбычный веб: Тему/цвета делаете сами
Передача данных
Telegram Mini App: Можно отправить через
sendData()(если открыть через нужную кнопку) или через бэкендОбычный веб: Только через ваш бэкенд
Лично для меня, когда я выбирал, на какой платформе делать приложение, хватило лишь первого пункта, чтобы выбрать MiniApp. Все остальное - приятные бонусы.
Практическая часть
Сейчас, как было обещано ранее, я покажу примеры реализации некоторых сценариев и запущу проект без настройки VPS в движке придожений Amvera Cloud, получив бесплатный внешний HTTPS-домен без ручной настройки сертификата.
1. Валидация initData
initData - это обычная строка в виде key1=value1&key=value2, которую сам Telegram кладет в объект WebApp.initData. Рядом с данными есть поле hash - это подпись, по которой мы и сможем понять, настоящие данные нам были переданы или нет.
Зачем вообще это нужно проверять? Потому что на фронте можно подменить любой значение. То есть человек в DevTools может подменить свой initData без каких-либо препятствий. Спасает только проверка подписи hash.
Источник: http://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app
Что нам нужно сделать для валидации:
На фронте берем объект
Telegram.WebApp.initDataи отправляем на бекенд.После этого сервер:
Достает
hashи убирает его из списка полей;Сортирует остальные поля по названию
Склеивает их через перенос строки (
\n) в одну строкуСчитает правильный хеш по алгоритму Telegram
Сравнивает с тем
hash, что пришел
Реализация алгоритма проверки на примере Python FastAPI
Для начала отправляем запрос с фронта:
const tg = window.Telegram?.WebApp;
async function auth() {
const r = await fetch("/api/tg/auth", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ initData: tg?.initData || "" })
});
if (!r.ok) throw new Error("initData некорректный или miniapp открыт не в Telegram");
return r.json();
}
Теперь принимаем запрос по /api/tg/auth на стороне бекенда:
import os, json, hmac, hashlib
from urllib.parse import parse_qsl
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "")
def validate_init_data(init_data):
data = dict(parse_qsl(init_data, keep_blank_values=True))
# Убираем hash, т.к. подпись считается ТОЛЬКО по остальным полям
got_hash = data.pop("hash", "")
if not got_hash:
return None
# Все параметры вписываем в одну строку
data_check_string = "\n".join(f"{k}={data[k]}" for k in sorted(data.keys()))
# Делаем секретный ключ по правилам Telegram (https://core.telegram.org/bots/webapps#validating-data-received-via-the-mini-app)
secret_key = hmac.new(b"WebAppData", BOT_TOKEN.encode(), hashlib.sha256).digest()
# Читаем подпись и сравниваем с тем хешем, что пришел в initData
calc_hash = hmac.new(secret_key, data_check_string.encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(calc_hash, got_hash):
return None
return json.loads(data["user"]) if "user" in data else None
@app.post("/api/tg/auth")
async def tg_auth(req: Request):
body = await req.json()
user = validate_init_data(body.get("initData", ""))
if not user:
raise HTTPException(401, "Поддельный initData")
return {
"ok": True,
}
Самое интересное - помимо проверки на валидность initData, мы ровно таким же принципом можем проверять, открыт Mini App через Telegram или с какого-нибудь браузера вне мессенджера.
2. Развертывание приложения
Подготовка кода
Вспомним то, что для развертывания такого Mini App, нам нужно два проекта: фронтенд и бекенд.
В Amvera Cloud для больших проектов я рекомендую развертывать именно два проекта - так можно избежать сильного понижения отказоустойчивости, что очень важно для нагруженных проектов.
Но у нас совсем простой проект, который просто рендерит уже готовый index.html на / маршруте и проверяет initData на маршруте /api/tg/auth, нам будет достаточно одного Python FastAPI проекта.
Структура проекта выглядит так:
.
├── Dockerfile
├── main.py
├── requirements.txt
└── templates
└── index.html
И сам код
main.py:
import os, json, hmac, hashlib
from urllib.parse import parse_qsl
from fastapi import FastAPI, Request, HTTPException
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
app = FastAPI()
templates = Jinja2Templates(directory="templates")
BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "")
BOT_URL = os.getenv("TELEGRAM_BOT_URL", "ссылка_на_бота")
def validate_init_data(init_data):
data = dict(parse_qsl(init_data, keep_blank_values=True))
got_hash = data.pop("hash", "")
if not got_hash:
return None
data_check_string = "\n".join(f"{k}={data[k]}" for k in sorted(data.keys()))
secret_key = hmac.new(b"WebAppData", BOT_TOKEN.encode(), hashlib.sha256).digest()
calc_hash = hmac.new(secret_key, data_check_string.encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(calc_hash, got_hash):
return None
return json.loads(data["user"]) if "user" in data else None
@app.get("/", response_class=HTMLResponse)
async def index(req: Request):
return templates.TemplateResponse("index.html", {"request": req, "bot_url": BOT_URL})
@app.post("/api/tg/auth")
async def tg_auth(req: Request):
if not BOT_TOKEN:
raise HTTPException(500, "TELEGRAM_BOT_TOKEN не задан на сервере")
body = await req.json()
user = validate_init_data(body.get("initData", ""))
if not user:
raise HTTPException(401, "initData некорректный или приложение открыто не в Telegram")
first = (user.get("first_name") or "").strip()
last = (user.get("last_name") or "").strip()
full_name = (first + " " + last).strip() or "друг"
return {"ok": True, "full_name": full_name}
templates/index.html:
<!doctype html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>MiniApp</title>
<style>
:root {
--bg: var(--tg-theme-bg-color, #ffffff);
--text: var(--tg-theme-text-color, #111111);
--hint: var(--tg-theme-hint-color, #777777);
--btn: var(--tg-theme-button-color, #2ea6ff);
--btnText: var(--tg-theme-button-text-color, #ffffff);
--card: rgba(0,0,0,.06);
}
body {
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
background: var(--bg);
color: var(--text);
}
.wrap {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
box-sizing: border-box;
}
.card {
width: 100%;
max-width: 520px;
border-radius: 16px;
padding: 18px;
background: color-mix(in srgb, var(--bg) 92%, #000 8%);
box-shadow: 0 10px 30px rgba(0,0,0,.08);
}
h1 {
margin: 0 0 8px 0;
font-size: 22px;
line-height: 1.2;
}
p {
margin: 0 0 14px 0;
color: var(--hint);
line-height: 1.45;
}
.row {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-top: 12px;
}
.btn {
appearance: none;
border: 0;
border-radius: 12px;
padding: 12px 14px;
font-weight: 600;
cursor: pointer;
background: var(--btn);
color: var(--btnText);
}
.btn.secondary {
background: rgba(0,0,0,.08);
color: var(--text);
}
.hidden { display: none; }
.error { color: #d23c3c; }
</style>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
</head>
<body>
<div class="wrap">
<div class="card">
<div id="ok" class="hidden">
<h1 id="hello">Привет!</h1>
<p>Все успешно!</p>
<div class="row">
<button class="btn" id="closeBtn">Закрыть MiniApp</button>
</div>
</div>
<div id="err" class="hidden">
<h1 class="error">Ошибка</h1>
<p>initData некорректный или приложение открыто не в Telegram</p>
<div class="row">
<a class="btn" id="openBtn" href="{{ bot_url }}">Открыть в Telegram</a>
<button class="btn secondary" id="retryBtn">Попробовать еще раз</button>
</div>
</div>
</div>
</div>
<script>
const tg = window.Telegram?.WebApp;
const okEl = document.getElementById("ok");
const errEl = document.getElementById("err");
const helloEl = document.getElementById("hello");
const closeBtn = document.getElementById("closeBtn");
const retryBtn = document.getElementById("retryBtn");
function showOk(fullName) {
helloEl.textContent = `Привет, ${fullName}!`;
errEl.classList.add("hidden");
okEl.classList.remove("hidden");
}
function showErr() {
okEl.classList.add("hidden");
errEl.classList.remove("hidden");
}
async function auth() {
try {
if (tg?.ready) tg.ready();
const r = await fetch("/api/tg/auth", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ initData: tg?.initData || "" })
});
if (!r.ok) throw new Error("bad initData");
const data = await r.json();
showOk(data.full_name || "!ошибка!");
} catch (e) {
showErr();
}
}
closeBtn.addEventListener("click", () => {
if (tg?.close) tg.close();
else window.close();
});
retryBtn.addEventListener("click", () => auth());
auth();
</script>
</body>
</html>
requirements.txt:
fastapi[standard]
uvicorn
jinja2
Dockerfile (файл с инструкциями для сборки и запуска):
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
Приложение работает по следующему принципу: если initData валидный, то отображается Mini App с приветствием по full_name, кнопкой "Закрыть Mini App" и темой пользователя. Если же он невалидный или сайт будет открыт не через Mini App - будет написано об ошибке.
Регистрация и деплой
Теперь, когда у нас готов код, мы можем перейти к последнему этапу - регистрации и деплою.
Как я уже говорил: проект будет развернут в Amvera Cloud - сервисе для простого деплоя IT-приложений в считанные минуты, который предоставляет множество бонусов, таких как:
Деплой проекта без нестройки сервера - обновления производятся через коммиты в привязанный Git или простым перетягиванием файлов в интерфейсе,
111 рублей на баланс для тестов сразу после регистрации и без дополнительных условий,
Бесплатный внешний домен с SSL-сертификатом для любого приложения,
Возможность деплоя приложения в разных регионах (как в Москве, так и в Варшаве),
Бесплатное встроенное проксирование до OpenAI, GrokAI, Gemini и прочих сервисов, которые блокируют соединения из РФ,
и многое другое.
Прежде чем начать создание проекта, нам нужно зарегистрироваться по ссылке.
Сразу после регистрации переходим на главную страницу проектов и жмём кнопку в правом верхнем углу - "Создать проект"

В открывшемся окне выбираем Тип сервиса: "Приложение", жмём "Далее".
Далее выбираем произвольное имя проекта и нужный тариф.
На этапе "Загрузка данных" переключаемся на режим "Через интерфейс" и грузим все наши файлы

Теперь мы можем создать необходимые для проекта переменные окружения. После создания уверенно жмём "Далее".
На последнем этапе проекта мы можем выбрать конфигурацию проекта. Так как мы используем Dockerfile, создавать конфигурацию (файл этой конфигурации будет называться
amvera.yml) не обязательно, мы можем просто нажать кнопку "Завершить".
Несмотря на то, что
amvera.ymlнеобязателен, хочу, чтоб вы понимали, что именно благодаря этому файлу и возможности его конфигурации через интерфейс и достигается такая простота в деплое.
Если вы все сделали правильно, появится проект с вашим именем в разделе "Приложения" сервиса. Открываем его, переходим во вкладку "Конфигурация" и жмём кнопку "Собрать". После этого статус проекта сменится на "Выполняется сборка".
Создание https домена для Web App
Теперь самое важное для Web App'a, без чего все действия ранее бесполезны - создание внешнего домена с SSL-сертификатом и типом подключения HTTPS.
Это делается буквально в несколько кликов:
Переходим во вкладку "Домены" проекта
Жмём кнопку "Создать доменное имя"
Выбираем:
Тип подключения: HTTPS
Тип домена: Бесплатный домен Amvera
Нажимаем "Применить"
Если у вас есть собственный домен, то вы можете привязать и его. Для этого вам понадобится прописать заданные A и TXT записи в настройках DNS вашего домена.
Итог
Если вы все делали по инструкции и статус проекта теперь "Приложение запущено" - вы можете открывать Telegram Mini App по созданному ранее домену.
Все, что нам остается - протестировать приложение.
Для начала добавим URL для MiniApp'а в настройки бота @BotFather.
Если открыть приложение с любого браузера или с некорректным initData, то покажется:

А если с MiniApp:

Простыми словами - все работает!
Надеюсь, что эта статья была для вас полезна и вы узнали что-то новое. Если у вас остались вопросы или вы заметили ошибку в статье - пишите в комментарии.
