# WWChat Mini Apps — complete reference for LLMs Machine-readable reference for building **WWChat Mini Apps** — web apps that open inside WWChat from a bot button. They are **Telegram Web Apps–compatible**: you build a normal website (HTML/CSS/JS), host it yourself, and WWChat launches it in an embedded webview, passes signed user data (`initData`), and exposes a JavaScript SDK (`window.WWChat.WebApp`) for buttons, theming, popups and talking back to the bot. Telegram Web App knowledge transfers directly (`window.Telegram.WebApp` → `window.WWChat.WebApp`). - Human docs: https://wwchat.org/docs/mini-apps - This file: https://wwchat.org/miniapps.txt - Bot API (to send buttons, payments, answerWebAppQuery): https://wwchat.org/botapi.txt The WWChat server only **signs the launch parameters** with your bot token. All app logic lives on **your** backend; WWChat never stores your Mini App code. ## How it works 1. The user taps a `web_app` button (under a message, or the bot menu button). 2. WWChat signs `initData` with your bot token and opens your URL in a webview. 3. Inside the webview `window.WWChat.WebApp` is available; the app calls `WebApp.ready()`. 4. The app sends `WebApp.initData` to **its own** backend. 5. The backend re-validates the HMAC with the bot token and trusts the user's identity. ## Connecting to a bot The app URL must be **https** (`http://localhost` allowed for local dev). A button's `web_app` field takes priority over `url`/`callback_data`. ### Option 1 — inline button under a message ``` POST /bot/v1/{token}/sendMessage { "chat_id": "550e8400-e29b-41d4-a716-446655440000", "text": "Open our shop:", "reply_markup": { "inline_keyboard": [ [ { "text": "🛒 Shop", "web_app": { "url": "https://shop.example.com/app" } } ] ] } } ``` ### Option 2 — bot menu button (persistent, next to the input field) ``` POST /bot/v1/{token}/setChatMenuButton { "menu_button": { "type": "web_app", "text": "Open app", "web_app": { "url": "https://shop.example.com/app" } } } ``` `getChatMenuButton` returns the current button; set `{ "type": "default" }` to remove. Can also be configured via @BotMama (bot settings → Mini App). ## JavaScript SDK (`window.WWChat.WebApp`) The SDK is injected automatically before your scripts run. For local dev you may include it explicitly (re-load is a no-op): ```html ``` ### Lifecycle ```js WWChat.WebApp.ready(); // UI ready — hide your loading splash WWChat.WebApp.expand(); // expand container to full height WWChat.WebApp.close(); // close the Mini App ``` ### Properties | property | description | |---|---| | `initData` | signed query string — send to your backend to validate | | `initDataUnsafe` | parsed object (`user`, `auth_date`, …); do NOT trust without validating | | `platform` | `ios` / `android` / `macos` / `windows` / `web` | | `colorScheme` | `light` or `dark` | | `themeParams` | host theme colors object | | `viewportHeight`, `viewportStableHeight` | viewport height in px | | `isExpanded` | whether the container is expanded | | `version` | SDK version | ### MainButton / SecondaryButton (bottom buttons; methods are chainable) ```js WWChat.WebApp.MainButton .setText('Pay 500 ⭐') .show() .onClick(function () { WWChat.WebApp.MainButton.showProgress(); // ... your logic, then hideProgress() ... }); // or in one call: WWChat.WebApp.MainButton.setParams({ text: 'Done', color: '#FF6B35', text_color: '#FFFFFF', is_active: true, is_visible: true }); ``` Methods: `setText`, `show`, `hide`, `enable`, `disable`, `showProgress`, `hideProgress`, `setParams`, `onClick`, `offClick`. `SecondaryButton` has the same API. ### BackButton ```js WWChat.WebApp.BackButton.show().onClick(function () { WWChat.WebApp.BackButton.hide(); }); ``` ### Haptic feedback ```js WWChat.WebApp.HapticFeedback.impactOccurred('medium'); // light | medium | heavy WWChat.WebApp.HapticFeedback.notificationOccurred('success'); // success | warning | error WWChat.WebApp.HapticFeedback.selectionChanged(); ``` ### Popups ```js WWChat.WebApp.showAlert('Saved'); WWChat.WebApp.showConfirm('Delete?', function (ok) { if (ok) remove(); }); WWChat.WebApp.showPopup({ title: 'Confirm', message: 'Are you sure?', buttons: [ { id: 'ok', text: 'OK' }, { id: 'cancel', text: 'Cancel' } ] }, function (buttonId) { /* the pressed button */ }); ``` ### Misc ```js WWChat.WebApp.openLink('https://example.com'); // open in external browser WWChat.WebApp.setHeaderColor('#FF6B35'); WWChat.WebApp.setBackgroundColor('#FFFFFF'); WWChat.WebApp.sendData('payload'); // send data back to the bot (keyboard Mini Apps) ``` ### Events ```js WWChat.WebApp.onEvent('themeChanged', function () { applyTheme(); }); WWChat.WebApp.onEvent('viewportChanged', function (e) { /* e.height, e.is_expanded */ }); ``` Events: `themeChanged`, `viewportChanged`, `mainButtonClicked`, `secondaryButtonClicked`, `backButtonClicked`. Unsubscribe with `offEvent(type, handler)`. ## Launch parameters (initData) `WebApp.initData` is the raw signed query string (forward as-is to your backend). `WebApp.initDataUnsafe` is the same parsed into an object — trust it only AFTER validating the signature on the backend. ```json { "auth_date": 1750000000, "user": { "id": "550e8400-e29b-41d4-a716-446655440000", "first_name": "John", "last_name": "Doe", "username": "john", "language_code": "en", "photo_url": "https://..." }, "query_id": "...", // optional — for answerWebAppQuery "chat_type": "private", // optional: private | group | channel "start_param": "promo_42", // optional: deep-link payload "hash": "abc123..." } ``` - `user.id` — WWChat user UUID. Phone and email are **not** passed. - `auth_date` — unix time of signing; valid ≈1 hour (client re-requests fresh on each launch). - `start_param` — arbitrary deep-link payload from the button/link. - `hash` — HMAC signature; validate on the backend (below). ## Validating initData (do this on your backend) Never trust `initDataUnsafe` on the client. The scheme is Telegram-Web-Apps-compatible: ``` secret_key = HMAC_SHA256(key="WebAppData", message=bot_token) hash = HEX( HMAC_SHA256(key=secret_key, message=data_check_string) ) ``` `data_check_string` = every field except `hash`, sorted by key, joined as `key=value` with `\n`. If the computed hash equals the received one, the data is authentic. ### Node.js ```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(e => 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; } ``` ### Python ```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) ``` Keep the bot token server-side only. After validation, also check `auth_date` to reject stale launches. ## Replying to the chat (answerWebAppQuery) `initData` includes a `query_id` — a signed token carrying the launch context (bot, user, chat). The bot posts a result back into the launch chat with no server-side state. Flow: Mini App gets a result → sends `initDataUnsafe.query_id` + result to its backend → the bot backend calls `answerWebAppQuery` → WWChat posts a message as the bot into the chat. ``` POST /bot/v1/{token}/answerWebAppQuery { "web_app_query_id": "", "text": "Order placed ✅", "reply_markup": { "inline_keyboard": [ [ { "text": "My orders", "callback_data": "orders" } ] ] } } ``` | param | type | description | |---|---|---| | web_app_query_id | String | the `query_id` value from initData | | text | String | message the bot posts into the chat | | reply_markup | ReplyMarkup | optional inline keyboard on the result | `query_id` is short-lived — answer soon after launch. If launched outside a chat, use a regular `sendMessage` instead. ## Theming ```js 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 () { /* re-read wa.themeParams */ }); ``` `themeParams` keys: `bg_color`, `text_color`, `hint_color`, `link_color`, `button_color`, `button_text_color`, `secondary_bg_color`. Tint the container via `setHeaderColor` / `setBackgroundColor`. ## Full example (minimal Mini App) ```html

``` From here it's plain web development. For bot methods (messages, star payments, file download) see the Bot API: https://wwchat.org/botapi.txt