# 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