Make a mini app for Garbo

A Garbo mini app is an ordinary website that opens inside the Garbo app, with Garbo's own buttons, the person's details (when they allow it) and storage. If you have made a Telegram Mini App, you already know how: the names are the same.

Quick start

  1. Make a website that works well on a phone (any tools, any host, https).
  2. Add Garbo's script to every page, in <head>, before your own scripts:
    <script src="https://developers.garbo.ph/garbo-web-app.js"></script>
  3. Call Garbo.WebApp.ready() as soon as your first screen shows: Garbo's loading screen goes away.
  4. Send Garbo.WebApp.initData to your server and check it there. It says who is using your app (or that nobody is logged in), signed by Garbo.
  5. 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).

FieldWhat it is
app_idYour app's id.
auth_dateWhen it was opened, in seconds since 1970. Refuse old launch data (a day, or less for anything sensitive).
start_paramFrom 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=.
userJSON: the person as your app sees them (next section). Absent for a visitor.
signatureGarbo's Ed25519 signature: anyone can check it with Garbo's public key.
hashHMAC-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 allowFields in user
(always)id, language_code
Nickname, picture and profile linkusername, profile_url, photo_url, and official_name for official accounts
Full namefirst_name, last_name
Town and provincetown, town_code (the PSGC code), province
Gendergender: male, female or prefer_not_to_say
When they joined Garbojoined_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

NameWhat it is
initData, initDataUnsafeThe launch data, as a string and read for you.
versionThe script's version in Garbo ("1.0"); isVersionAtLeast("1.0").
platformios, android, web, or unknown outside Garbo.
colorScheme, themeParamsGarbo's colours (light for now), also as CSS variables: var(--garbo-theme-bg-color), --garbo-theme-button-color, …
viewportHeight, viewportStableHeightYour page's height between Garbo's bars (--garbo-viewport-height).
safeAreaInset, contentSafeAreaInsetAlways 0 today: Garbo keeps your page clear of the notch and the home bar.
isActive, isExpandedIn front (not behind another Garbo page or in the background); always expanded.
headerColor, backgroundColor, bottomBarColor, isClosingConfirmationEnabledWhat you set (below).

Garbo's buttons

NameWhat it does
MainButton, SecondaryButtonDrawn 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.
BackButtonA 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

NameWhat it does
ready(), close(), expand()Hides the loading screen; closes your app; (always full screen).
showPopup({ title, message, buttons }, cb), showAlert, showConfirmGarbo's own box with your app's name on it; up to 3 buttons (default, ok, close, cancel, destructive).
HapticFeedbackimpactOccurred(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, SecureStorageSee Storage.
onEvent(name, handler), offEventactivated, deactivated, viewportChanged, themeChanged, mainButtonClicked, secondaryButtonClicked, backButtonClicked, settingsButtonClicked, popupClosed, homeScreenAdded, homeScreenChecked, profileAccessRequested, contactRequested, emailRequested, fileDownloadRequested, shareClosed.

Types for TypeScript: garbo-web-app.d.ts.

Design

Storage, contacts, sharing

StorageWhereLimits
CloudStorageGarbo 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
DeviceStorageThis phone only5 MB
SecureStorageThis phone's Keychain / Keystore10 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

Moving from Telegram

TelegramGarbo
telegram-web-app.js, window.Telegram.WebAppgarbo-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
openTelegramLinkopenGarboLink
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.

Files

garbo-web-app.jsThe script (its address never changes)
garbo-web-app.d.tsTypeScript 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.txtGarbo's public key