Quick start
- Make a website that works well on a phone (any tools, any host, https).
- Add Garbo's script to every page, in
<head>, before your own scripts:<script src="https://developers.garbo.ph/garbo-web-app.js"></script> - Call
Garbo.WebApp.ready()as soon as your first screen shows: Garbo's loading screen goes away. - Send
Garbo.WebApp.initDatato your server and check it there. It says who is using your app (or that nobody is logged in), signed by Garbo. - Ask Garbo to list your app: its name (12 characters at most), picture, address, what it asks to know, its age rating and its privacy policy. Garbo gives you the app's id and its secret, and checks every app before it goes live. (Publishing will be open to everyone through Garbo's Outlets.)
The starter is a page and a small server with all of this; the sample app shows every part of the script.
The launch data
When Garbo opens your app it gives your page Garbo.WebApp.initData: a URL-encoded string of these fields
(initDataUnsafe is the same, read for you; use it to show things, never to decide things).
| Field | What it is |
|---|---|
app_id | Your app's id. |
auth_date | When it was opened, in seconds since 1970. Refuse old launch data (a day, or less for anything sensitive). |
start_param | From the link that opened your app: https://garbo.ph/app/<your short name>?start=promo_10
(up to 512 letters, digits, _ and -). Also in your page's address as ?garboWebAppStartParam=. |
user | JSON: the person as your app sees them (next section). Absent for a visitor. |
signature | Garbo's Ed25519 signature: anyone can check it with Garbo's public key. |
hash | HMAC-SHA-256 with a key made from your app's secret: your server checks it. |
What your app may know
Without asking: an id made for your app only (a number; the same person has a different id in every other app, so apps can't follow people around), the language, the kind of phone, the colours, the start parameter. Everything else only after the person allows it on Garbo's sheet, the first time they open your app. You say which items your app needs when it's listed; people may switch any of them off, and take them back later in Garbo.
| Item they allow | Fields in user |
|---|---|
| (always) | id, language_code |
| Nickname, picture and profile link | username, profile_url, photo_url, and official_name for official accounts |
| Full name | first_name, last_name |
| Town and province | town, town_code (the PSGC code), province |
| Gender | gender: male, female or prefer_not_to_say |
| When they joined Garbo | joined_month: "YYYY-MM" |
Asked every time, with Garbo's own question: the phone number (requestContact) and a confirmed email
(requestEmail). Never: birth date or age (Garbo checks your app's age rating itself), Garbo's login or
Garbo's own id for the person, their PIN or codes, whom they follow, their activity.
A visitor (nobody logged in) has no user. Garbo.WebApp.requestLogin() opens Garbo's Login;
once they're in, your page opens again with their launch data. If your app only works for people with an account,
say so when it's listed: Garbo asks visitors to log in first.
Checking the launch data
The same steps as Telegram's, with your app's secret in place of a bot token. The check string is every field
except the ones left out, sorted by name, each as name=value (decoded), joined with a line feed.
With your app's secret (on your server)
secret_key = HMAC_SHA256(key = "WebAppData", message = <your app's secret>)
hash = hex(HMAC_SHA256(key = secret_key, message = check string without "hash"))
With Garbo's public key (no secret needed)
message = <app_id> + ":WebAppData\n" + check string without "hash" and "signature"
Ed25519 verify(Garbo's public key, base64url-decoded signature, message)
Garbo's public key (Ed25519, hex): a6b7e2bcbd35fa46f8040e430316409d1d6e24f98f5232e5d89b9c361f7525a0
Ready to copy: Node.js, Deno, Bun, Cloudflare Workers, Python, PHP.
import { checkHash } from './node.mjs';
const data = checkHash(initData, process.env.GARBO_APP_SECRET); // throws if it isn't Garbo's
console.log(data.user?.id); // the person's id in your app
Keep your app's secret on your server: never in your page. If it leaks, ask Garbo for a new one.
Garbo.WebApp
Properties
| Name | What it is |
|---|---|
initData, initDataUnsafe | The launch data, as a string and read for you. |
version | The script's version in Garbo ("1.0"); isVersionAtLeast("1.0"). |
platform | ios, android, web, or unknown outside Garbo. |
colorScheme, themeParams | Garbo's colours (light for now), also as CSS variables: var(--garbo-theme-bg-color), --garbo-theme-button-color, … |
viewportHeight, viewportStableHeight | Your page's height between Garbo's bars (--garbo-viewport-height). |
safeAreaInset, contentSafeAreaInset | Always 0 today: Garbo keeps your page clear of the notch and the home bar. |
isActive, isExpanded | In front (not behind another Garbo page or in the background); always expanded. |
headerColor, backgroundColor, bottomBarColor, isClosingConfirmationEnabled | What you set (below). |
Garbo's buttons
| Name | What it does |
|---|---|
MainButton, SecondaryButton | Drawn by Garbo below your page: setText, setParams({ text, color, text_color, is_visible, is_active, position }),
show, hide, enable, disable, showProgress, hideProgress, onClick, offClick.
The Secondary one sits left, right, top or bottom of the Main one. |
BackButton | A back arrow in Garbo's bar in place of Close; Android's back button presses it too. |
SettingsButton | "Settings" in your app's "⋯" menu in Garbo. |
Methods
| Name | What it does |
|---|---|
ready(), close(), expand() | Hides the loading screen; closes your app; (always full screen). |
showPopup({ title, message, buttons }, cb), showAlert, showConfirm | Garbo's own box with your app's name on it; up to 3 buttons (default, ok, close, cancel, destructive). |
HapticFeedback | impactOccurred(style), notificationOccurred(type), selectionChanged(). |
setHeaderColor, setBackgroundColor, setBottomBarColor | "#RRGGBB" or a theme key (bg_color, secondary_bg_color). |
enableClosingConfirmation(), disableClosingConfirmation() | Garbo asks before the person closes your app. |
openLink(url), openGarboLink(url) | A website in the browser; a Garbo page (another mini app, a profile, a post, a search). |
addToHomeScreen(), checkHomeScreenStatus(cb) | Garbo asks the person; your app's tile goes on their Garbo home screen. |
requestProfileAccess(cb) | Garbo's sheet again (after "Not now"); initData is updated when they allow. |
requestLogin() | Garbo's Login, for a visitor. |
requestContact(cb), requestEmail(cb) | The phone number or confirmed email, after Garbo's question; signed like initData. |
downloadFile({ url, file_name }, cb) | Garbo asks, then the file opens in the browser. |
share({ text, url }, cb) | In a Garbo post or the phone's share sheet. |
CloudStorage, DeviceStorage, SecureStorage | See Storage. |
onEvent(name, handler), offEvent | activated, deactivated, viewportChanged, themeChanged, mainButtonClicked,
secondaryButtonClicked, backButtonClicked, settingsButtonClicked, popupClosed, homeScreenAdded,
homeScreenChecked, profileAccessRequested, contactRequested, emailRequested, fileDownloadRequested, shareClosed. |
Types for TypeScript: garbo-web-app.d.ts.
Design
- Garbo draws a bar above your page with your app's picture and name and "Mini app" under it, Close (or your back arrow) and "⋯" (Reload, Share, Add to Home Screen, About, Report, Block). Don't draw your own close button.
- Your picture (square, at least 192×192) and two colours (background and text) make your loading screen.
Call
ready()early. - Use the theme's CSS variables so your app looks at home; put your main action in the
MainButton. - Make it fast on a cheap phone and a slow connection: small pages, small pictures, no huge libraries.
Links and addresses
- Your app stays on its own address and the other hosts it was listed with (a checkout on another domain, say).
Any other website opens in the browser; if your site moves from
example.comtowww.example.com, list both. - Links to other apps (
tel:,mailto:,sms:, an e-wallet's link) open them after Garbo asks the person. - Frames inside your page (maps, videos, ads) load, but can't use
Garbo.WebApp. - Garbo puts its launch parameters after
#in your address the first time (the script keeps them for the tab). If your app uses the address's#for its own pages, load the script first. - Links to your app:
https://garbo.ph/app/<short name>?start=…(coming with Garbo's website).
Storage, contacts, sharing
| Storage | Where | Limits |
|---|---|---|
CloudStorage | Garbo keeps it for the person, on every phone they use (logged in only) | 1,024 keys of 1–128 letters, digits, _, -; values up to 4,096 characters |
DeviceStorage | This phone only | 5 MB |
SecureStorage | This phone's Keychain / Keystore | 10 values; no restoreItem (values never leave the phone) |
Callbacks are (error, value), as in Telegram. A person who disconnects your app in Garbo's settings loses what
Garbo kept for your app; you must delete your own copy of their data when they ask.
Testing your app
- Until your app is live it's a draft: only you (your Garbo account) and Garbo can open it. A draft may use
http://, so you can point it at your computer (same Wi-Fi) or a tunnel while you build. - In a browser, outside Garbo, the script works on its own: popups use the browser's boxes, links open in a new tab,
the rest does nothing, and
platformisunknown. - Debugging inside Garbo: Safari's Web Inspector (iPhone, with Web Inspector on in Settings → Safari → Advanced) and
Chrome's
chrome://inspect(Android, with USB debugging) work for your own drafts.
Moving from Telegram
| Telegram | Garbo |
|---|---|
telegram-web-app.js, window.Telegram.WebApp | garbo-web-app.js, window.Garbo.WebApp |
| The bot token (hash check) | Your app's secret |
<bot_id>:WebAppData and Telegram's public key | <app_id>:WebAppData and Garbo's public key |
user.id (the same everywhere) | user.id, made for your app only |
openTelegramLink | openGarboLink |
startapp in a t.me link | ?start= in a garbo.ph/app/… link |
Not in Garbo yet (they do nothing, with a warning, so your app doesn't break): sendData, inline queries,
invoices and Stars, stories, emoji status, the QR scanner, the clipboard, full screen and orientation, biometrics,
location, motion sensors. Garbo's own: requestProfileAccess, requestLogin, requestEmail,
share, openGarboLink.
Rules for mini apps
A first version, to be written with Garbo's lawyer. Garbo checks every app against them before it goes live, and may pause or remove an app that breaks them.
- Never ask for a person's Garbo PIN or the codes Garbo sends them.
- No gambling, nothing illegal, nothing pretending to be someone else; an honest age rating.
- Use people's details only for your app's own service; never sell or pass them on; delete them when the person asks or disconnects your app; have your own privacy policy.
- Real goods and services (food, deliveries, rides, tickets) use your own checkout. Digital goods (coins, items, subscriptions) aren't allowed for now: they will go through Garbo's coins.
- If people post things in your app, give them a way to report and block, and act on reports.
Files
| garbo-web-app.js | The script (its address never changes) |
| garbo-web-app.d.ts | TypeScript types |
| check/ | Checking the launch data: Node.js, Deno, Python, PHP |
| starter/ | A page and a small server (server.mjs) to start from |
| public-key.txt | Garbo's public key |