Mini Apps — это веб-приложения, которые открываются прямо внутри WWChat по кнопке бота. По логике это аналог Telegram Web Apps: вы пишете обычный сайт (HTML/CSS/JS), хостите его у себя, а WWChat запускает его во встроенном webview, передаёт подписанные данные о пользователе и даёт JavaScript SDK для кнопок, темы, всплывающих окон и связи с ботом.
Сервер WWChat только подписывает параметры запуска (initData) на токене вашего бота. Вся логика приложения — на вашем бэкенде; код Mini App у WWChat не хранится.
Как это работает
- Пользователь нажимает кнопку
web_app(под сообщением или меню-кнопку бота). - WWChat подписывает
initDataна токене вашего бота и открывает ваш URL в webview. - В webview доступен
window.WWChat.WebApp. Приложение вызываетWebApp.ready(). - Приложение отправляет
WebApp.initDataна свой бэкенд. - Бэкенд проверяет HMAC-подпись токеном бота — и доверяет личности пользователя.
Подключение к боту
Mini App запускается по кнопке с полем web_app. URL приложения должен быть https (для локальной разработки допускается http://localhost).
Способ 1. Inline-кнопка под сообщением
Добавьте кнопку с web_app в inline_keyboard при отправке сообщения (см. sendMessage):
POST /bot/v1/{token}/sendMessage
{
"chat_id": "550e8400-e29b-41d4-a716-446655440000",
"text": "Открой наш магазин:",
"reply_markup": {
"inline_keyboard": [
[ { "text": "🛒 Магазин", "web_app": { "url": "https://shop.example.com/app" } } ]
]
}
} Если у кнопки есть поле web_app, оно имеет приоритет над url и callback_data.
Способ 2. Меню-кнопка бота
Меню-кнопка — постоянная кнопка рядом с полем ввода в чате с ботом. Установите её один раз методом setChatMenuButton:
POST /bot/v1/{token}/setChatMenuButton
{
"menu_button": {
"type": "web_app",
"text": "Открыть приложение",
"web_app": { "url": "https://shop.example.com/app" }
}
} Текущую кнопку возвращает getChatMenuButton. Чтобы убрать Mini App, установите { "type": "default" }. То же самое можно сделать без кода — через @BotMama (настройки бота → Mini App).
JavaScript SDK
SDK инжектится в страницу автоматически ещё до запуска ваших скриптов, поэтому window.WWChat.WebApp уже доступен. Для локальной разработки можно подключить его явно (повторная загрузка — no-op):
<script src="https://api.wwchat.org/miniapp/wwchat-web-app.js"></script> Жизненный цикл
WWChat.WebApp.ready(); // интерфейс готов — убрать заглушку загрузки
WWChat.WebApp.expand(); // развернуть контейнер на всю высоту
WWChat.WebApp.close(); // закрыть Mini App Свойства
| Свойство | Описание |
|---|---|
initData | Подписанная строка запроса. Передайте её на свой бэкенд для проверки. |
initDataUnsafe | Распарсенный объект (user, auth_date, …). Не доверять без проверки подписи. |
platform | Платформа хоста: ios / android / macos / windows / web. |
colorScheme | light или dark. |
themeParams | Объект цветов темы хоста. |
viewportHeight, viewportStableHeight | Высота вьюпорта (px). |
isExpanded | Развёрнут ли контейнер. |
version | Версия SDK. |
Главная и дополнительная кнопки
MainButton и SecondaryButton — нижние кнопки контейнера, которыми управляет приложение. Все методы возвращают саму кнопку (можно чейнить).
WWChat.WebApp.MainButton
.setText('Оплатить 500 ⭐')
.show()
.onClick(function () {
WWChat.WebApp.MainButton.showProgress();
// ... ваша логика, затем hideProgress() ...
});
// или гибко одним вызовом:
WWChat.WebApp.MainButton.setParams({
text: 'Готово',
color: '#FF6B35',
text_color: '#FFFFFF',
is_active: true,
is_visible: true
}); Методы: setText, show, hide, enable, disable, showProgress, hideProgress, setParams, onClick, offClick.
Кнопка «Назад»
WWChat.WebApp.BackButton.show().onClick(function () {
WWChat.WebApp.BackButton.hide();
}); Тактильная отдача
WWChat.WebApp.HapticFeedback.impactOccurred('medium'); // light | medium | heavy
WWChat.WebApp.HapticFeedback.notificationOccurred('success'); // success | warning | error
WWChat.WebApp.HapticFeedback.selectionChanged(); Всплывающие окна
WWChat.WebApp.showAlert('Сохранено');
WWChat.WebApp.showConfirm('Удалить?', function (ok) { if (ok) remove(); });
WWChat.WebApp.showPopup({
title: 'Подтверждение',
message: 'Точно продолжить?',
buttons: [ { id: 'ok', text: 'OK' }, { id: 'cancel', text: 'Отмена' } ]
}, function (buttonId) { /* нажатая кнопка */ }); Прочее
WWChat.WebApp.openLink('https://example.com'); // открыть во внешнем браузере
WWChat.WebApp.setHeaderColor('#FF6B35');
WWChat.WebApp.setBackgroundColor('#FFFFFF');
WWChat.WebApp.sendData('payload'); // вернуть данные боту (keyboard Mini Apps) События
WWChat.WebApp.onEvent('themeChanged', function () { applyTheme(); });
WWChat.WebApp.onEvent('viewportChanged', function (e) { /* e.height, e.is_expanded */ }); Доступные события: themeChanged, viewportChanged, mainButtonClicked, secondaryButtonClicked, backButtonClicked. Отписаться — offEvent(type, handler).
Параметры запуска
WebApp.initData — сырая подписанная строка запроса (передаётся на ваш бэкенд как есть). WebApp.initDataUnsafe — тот же набор, распарсенный в объект для UI. «Unsafe» — потому что доверять ему можно только после проверки подписи на бэкенде.
{
"auth_date": 1750000000,
"user": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"first_name": "Иван",
"last_name": "Петров",
"username": "ivan",
"language_code": "ru",
"photo_url": "https://..."
},
"query_id": "...", // опционально
"chat_type": "private", // опционально: private | group | channel
"start_param": "promo_42", // опционально: deep-link payload
"hash": "abc123..."
} | Поле | Описание |
|---|---|
user.id | UUID пользователя WWChat. |
user.first_name, last_name, username | Профиль. Телефон и почта не передаются. |
user.language_code | Язык интерфейса, например ru. |
auth_date | Unix-время подписи. Проверяйте свежесть (≈1 час). |
chat_type | Тип чата запуска: private / group / channel. |
start_param | Произвольный deep-link payload (из кнопки/ссылки). |
hash | HMAC-подпись. Проверяется на бэкенде (см. ниже). |
Подпись живёт около часа (по auth_date) — клиент перезапрашивает свежую при каждом запуске.
Проверка подписи initData
Никогда не доверяйте initDataUnsafe на клиенте. Передайте initData на свой бэкенд и пересчитайте HMAC токеном бота — схема совместима с Telegram Web Apps:
secret_key = HMAC_SHA256(key="WebAppData", message=bot_token)
hash = HEX( HMAC_SHA256(key=secret_key, message=data_check_string) ) где data_check_string — все поля, кроме hash, отсортированные по ключу и склеенные как key=value через перевод строки. Если вычисленный hash совпал с полученным — данные подлинные.
Node.js
const crypto = require('crypto');
function checkInitData(initData, botToken) {
const p = new URLSearchParams(initData);
const hash = p.get('hash');
p.delete('hash');
const dataCheckString = [...p.entries()]
.map(function (e) { return e[0] + '=' + e[1]; })
.sort()
.join('\n');
const secret = crypto.createHmac('sha256', 'WebAppData').update(botToken).digest();
const calc = crypto.createHmac('sha256', secret).update(dataCheckString).digest('hex');
return calc === hash; // true → подпись валидна
} Python
import hmac, hashlib
from urllib.parse import parse_qsl
def check_init_data(init_data: str, bot_token: str) -> bool:
pairs = dict(parse_qsl(init_data))
received = pairs.pop('hash', '')
data_check = '\n'.join(k + '=' + pairs[k] for k in sorted(pairs))
secret = hmac.new(b'WebAppData', bot_token.encode(), hashlib.sha256).digest()
calc = hmac.new(secret, data_check.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(calc, received) Токен бота держите только на сервере. После проверки можно дополнительно сверить auth_date (отклонять слишком старые запуски).
Ответ бота в чат (answerWebAppQuery)
В initData приходит query_id — подписанный токен с контекстом запуска (бот, пользователь, чат). Через него бот публикует результат работы Mini App обратно в чат запуска — без хранения состояния на стороне WWChat.
Поток
- Mini App получает результат от пользователя (например, оформленный заказ).
- Отправляет
WebApp.initDataUnsafe.query_idи сам результат на свой бэкенд. - Бэкенд бота вызывает
answerWebAppQueryс этимquery_id. - WWChat публикует сообщение от имени бота в исходном чате.
POST /bot/v1/{token}/answerWebAppQuery
{
"web_app_query_id": "<query_id из initData>",
"text": "Заказ оформлен ✅",
"reply_markup": {
"inline_keyboard": [
[ { "text": "Мои заказы", "callback_data": "orders" } ]
]
}
} | Параметр | Тип | Описание |
|---|---|---|
web_app_query_id | String | Значение query_id из initData. |
text | String | Текст сообщения, которое бот опубликует в чате. |
reply_markup | ReplyMarkup | Опционально: inline-клавиатура к результату. |
У query_id ограниченный срок действия — отвечайте вскоре после запуска. Ответ уходит в тот чат, из которого открыт Mini App; если он открыт вне чата, используйте обычный sendMessage.
Тема и оформление
WWChat сообщает приложению текущую тему, чтобы оно выглядело «родным»:
var wa = WWChat.WebApp;
document.body.dataset.scheme = wa.colorScheme; // light | dark
var p = wa.themeParams || {};
document.body.style.background = p.bg_color || '#fff';
document.body.style.color = p.text_color || '#000';
// реагировать на смену темы на лету:
wa.onEvent('themeChanged', function () { /* перечитать wa.themeParams */ }); themeParams — объект цветов хоста (типичные ключи: bg_color, text_color, hint_color, link_color, button_color, button_text_color, secondary_bg_color). Заголовок и фон контейнера можно подкрасить вручную: setHeaderColor / setBackgroundColor.
Полный пример
Минимальный Mini App: приветствует пользователя, отправляет initData на проверку и показывает главную кнопку.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1 id="hi">…</h1>
<script src="https://api.wwchat.org/miniapp/wwchat-web-app.js"></script>
<script>
var wa = WWChat.WebApp;
wa.ready();
wa.expand();
var u = wa.initDataUnsafe.user || {};
document.getElementById('hi').textContent = 'Привет, ' + (u.first_name || 'гость') + '!';
// ВАЖНО: подлинность проверяем на бэкенде, не доверяя initDataUnsafe
fetch('/api/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ initData: wa.initData })
});
wa.MainButton.setText('Продолжить').show().onClick(function () {
wa.showAlert('Готово!');
wa.close();
});
</script>
</body>
</html> Дальше — обычная веб-разработка. Вопросы по методам бота (отправка сообщений, оплата звёздами) — в Bot API.