What it is
A mini app is your web page that opens on top of the chat with a bot. The user doesn't need to install anything or sign in: Telenoda passes the page signed data about the user and the chat, the color theme and the window size.
Suitable for stores, appointment booking, forms, personal dashboards and games. The app runs in an isolated window with no access to the account or Telenoda data.
Domains
First, specify the app's domains in “My bots” — up to 10 per bot. Telenoda will only open an https:// address on one of these domains or their subdomains. IP addresses, internal zones and Telenoda domains can't be specified. If the domains change, buttons with old addresses stop opening.
Launch methods
| Source | What the app receives | How to return a result |
|---|---|---|
A web_app button under a message | initData with query_id | the bot's server calls answerWebAppQuery, and the message is sent to the chat from the user “via bot” |
| Menu button | initData with query_id | answerWebAppQuery |
A web_app button in a keyboard instead of the system one | initData without query_id | Telenoda.WebApp.sendData — once, up to 4096 bytes; the bot receives message.web_app_data |
A ?startapp= link and the attachment menu | initData with start_param | answerWebAppQuery |
On the first launch of a bot's app, Telenoda asks the user for permission.
Setup
Include the bridge script in the page's <head> before your own scripts. It creates the window.Telenoda.WebApp object.
<script src="https://web.telenoda.org/clouchat-web-app.js"></script>
<script>
const app = window.Telenoda.WebApp;
app.ready();
app.expand();
document.body.style.background = app.themeParams.bg_color || "#ffffff";
app.MainButton.setText("Place order");
app.MainButton.onClick(async () => {
app.MainButton.showProgress();
await fetch("/api/order", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ initData: app.initData, items: cart }),
});
app.close();
});
app.MainButton.show();
</script>
Launch data validation
initData is a query string with the fields query_id, user, chat_type, chat_instance, start_param, auth_date and the signature hash. Ready-made launch data validators from bot libraries work without changes.
Don't trust initDataUnsafe on the server. Send the whole initData string to your server and verify the signature with the bot token.
import hashlib
import hmac
import time
from urllib.parse import parse_qsl
def check_init_data(init_data: str, token: str, max_age: int = 3600) -> dict:
fields = dict(parse_qsl(init_data, strict_parsing=True))
received = fields.pop("hash", "")
check_string = "\n".join(f"{key}={value}" for key, value in sorted(fields.items()))
secret = hmac.new(b"WebAppData", token.encode(), hashlib.sha256).digest()
expected = hmac.new(secret, check_string.encode(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, received):
raise PermissionError("bad signature")
if time.time() - int(fields["auth_date"]) > max_age:
raise PermissionError("init data expired")
return fields
The verification key is the HMAC-SHA256 of the bot token with the key "WebAppData"; the signature is the HMAC-SHA256 of the string of sorted key=value pairs joined by line breaks.
The Telenoda.WebApp object
| Field or method | Purpose |
|---|---|
initData, initDataUnsafe | launch data: a string for the server and a parsed object for the interface |
version, platform, isVersionAtLeast() | bridge version and client platform |
themeParams, colorScheme | Telenoda theme colors and the light or dark scheme |
ready(), expand(), close() | ready, expand to full height, close |
MainButton | main button at the bottom: setText, show, hide, enable, disable, showProgress, setParams, onClick |
BackButton | “Back” button in the header: show, hide, onClick |
HapticFeedback | impactOccurred, notificationOccurred, selectionChanged |
CloudStorage | key–value storage per user and bot |
sendData() | send data to the bot (launch from a keyboard) |
openLink(), openTelenodaLink() | open an external link in the browser or a me.telenoda.org link in Telenoda |
showPopup(), showAlert(), showConfirm() | system dialogs, up to three buttons |
requestContact() | ask the user to share their phone number |
setHeaderColor(), setBackgroundColor() | header and window background colors |
enableClosingConfirmation() | confirm before closing |
requestFullscreen(), exitFullscreen(), safeAreaInset, contentSafeAreaInset | full-screen mode and safe area insets |
addToHomeScreen(), checkHomeScreenStatus() | app shortcut on the home screen |
onEvent(), offEvent() | event subscription |
Events
themeChanged, viewportChanged, mainButtonClicked, backButtonClicked, popupClosed, contactRequested, fullscreenChanged, fullscreenFailed, safeAreaChanged, contentSafeAreaChanged, homeScreenAdded, homeScreenFailed, homeScreenChecked.
Theme
Telenoda passes the current theme colors in themeParams in #rrggbb format: bg_color, secondary_bg_color, text_color, hint_color, link_color, button_color, button_text_color, header_bg_color, bottom_bar_bg_color, accent_text_color, section_bg_color, section_header_text_color, subtitle_text_color, destructive_text_color, section_separator_color. When the theme changes, themeChanged fires.
CloudStorage
Up to 1024 keys per user and bot. A key is 1–128 characters A-Z a-z 0-9 _ -, a value is up to 4096 characters. The storage is available after the user has launched the bot's mini app.
const storage = window.Telenoda.WebApp.CloudStorage;
storage.setItem("theme", "dark", (error, saved) => {
if (error) return console.warn(error);
storage.getItem("theme", (error, value) => console.log(value));
});
Methods: setItem, getItem, getItems, removeItem, removeItems, getKeys.
Security
- Validate
initDataon your server with every request and limit its age byauth_date. - Don't keep the bot token in the page code: do everything that requires the token on the server.
- Recalculate prices, stock and permissions on the server without trusting data from the page.
- If the app processes personal data, publish your own data processing policy.
The Bot API methods for mini apps — answerWebAppQuery and setChatMenuButton — are described in the reference.