Mini Apps

RU EN

Mini Apps — это веб-приложения, которые открываются прямо внутри WWChat по кнопке бота. По логике это аналог Telegram Web Apps: вы пишете обычный сайт (HTML/CSS/JS), хостите его у себя, а WWChat запускает его во встроенном webview, передаёт подписанные данные о пользователе и даёт JavaScript SDK для кнопок, темы, всплывающих окон и связи с ботом.

Сервер WWChat только подписывает параметры запуска (initData) на токене вашего бота. Вся логика приложения — на вашем бэкенде; код Mini App у WWChat не хранится.

Как это работает

  1. Пользователь нажимает кнопку web_app (под сообщением или меню-кнопку бота).
  2. WWChat подписывает initData на токене вашего бота и открывает ваш URL в webview.
  3. В webview доступен window.WWChat.WebApp. Приложение вызывает WebApp.ready().
  4. Приложение отправляет WebApp.initData на свой бэкенд.
  5. Бэкенд проверяет 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.
colorSchemelight или 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.idUUID пользователя WWChat.
user.first_name, last_name, usernameПрофиль. Телефон и почта не передаются.
user.language_codeЯзык интерфейса, например ru.
auth_dateUnix-время подписи. Проверяйте свежесть (≈1 час).
chat_typeТип чата запуска: private / group / channel.
start_paramПроизвольный deep-link payload (из кнопки/ссылки).
hashHMAC-подпись. Проверяется на бэкенде (см. ниже).

Подпись живёт около часа (по 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.

Поток

  1. Mini App получает результат от пользователя (например, оформленный заказ).
  2. Отправляет WebApp.initDataUnsafe.query_id и сам результат на свой бэкенд.
  3. Бэкенд бота вызывает answerWebAppQuery с этим query_id.
  4. 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_idStringЗначение query_id из initData.
textStringТекст сообщения, которое бот опубликует в чате.
reply_markupReplyMarkupОпционально: 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.