Mini App configuration
A connected Mini App needs settings in three places: the Mini App's Vite build,
the API, and Telegram itself (BotFather and the bot webhook). Secrets live only
in the API. Every VITE_ value is compiled into the JavaScript bundle and is
public.
:::danger Never put secrets in VITE_ variables
Bot tokens, webhook secrets, the assertion private key and Supabase keys are
read only by the API. Anything prefixed with VITE_ ends up in the browser.
:::
Client Mini App
File: apps/client_tg_app/.env.local (start from .env.example).
# Read only by the local bot.py launcher, never by the Vite build.
TELEGRAM_BOT_TOKEN=
VITE_API_URL=http://127.0.0.1:3000
VITE_CHANNEL_AUTH_APP=client
| Variable | Read by | Required | Behavior |
|---|---|---|---|
VITE_API_URL | Vite build | For a production build | Public API origin for sign-in (/auth/*) and business requests. If empty, a development build uses http://127.0.0.1:3000, and a production build calls paths on its own origin. Set it at build time; changing it later does not change a built bundle |
VITE_CHANNEL_AUTH_APP | Vite build | No | Channel-auth app ID sent when creating an exchange context. Defaults to client. Must be a key in the API's CHANNEL_AUTH_TELEGRAM_APPS_JSON whose role is client |
TELEGRAM_BOT_TOKEN | bot.py only | For bot.py | Token of a test client bot used by the polling launcher. Not used by the app or the API |
Professional (master) Mini App
File: apps/master_tg_app/.env (start from .env.example).
# Read only by bot/bot.py.
BOT_TOKEN=
WEBAPP_URL=https://your-public-https-host
VITE_API_URL=
VITE_CHANNEL_AUTH_APP=professional
VITE_SUPPORT_CHAT_URL=
| Variable | Read by | Required | Behavior |
|---|---|---|---|
VITE_API_URL | Vite build | No | API origin. Leave empty to call the page's own origin; the dev and preview servers then proxy /auth and /professional to VITE_API_PROXY_TARGET, so a single tunnel serves both the app and the API. Set it for a production build hosted apart from the API |
VITE_CHANNEL_AUTH_APP | Vite build | No | Channel-auth app ID. Defaults to professional. Must be a key in CHANNEL_AUTH_TELEGRAM_APPS_JSON whose role is professional |
VITE_SUPPORT_CHAT_URL | Vite build | No | Telegram link opened by the support entry in the More screen. When empty, that entry does nothing |
VITE_CLIENT_WEB_PREVIEW_URL | Vite build | No | Origin of a running client web app used by the onboarding test-booking preview. Without it, the normal Bookygram schedule URL is used. Not listed in .env.example |
BOT_TOKEN | bot/bot.py only | For the launcher | Token of a test professional bot used by the polling launcher |
WEBAPP_URL | bot/bot.py only | For the launcher | Public HTTPS URL the bot's menu button and /start button open. run_preview.py overrides it with its tunnel domain |
Process environment read by vite.config.ts, not by the bundle:
| Variable | Default | Behavior |
|---|---|---|
VITE_API_PROXY_TARGET | http://127.0.0.1:3000 | Where the dev and preview servers proxy /auth and /professional |
BOOKYGRAM_PREVIEW | unset | Set to 1 by run_preview.py. The build then emits preview-version.json and a script that reloads open pages after a rebuild. Never set it for a real build |
API settings the Mini Apps depend on
Set these in apps/api/.env (development) or the deployment's secret store.
The full validation rules are in
Environment variables.
| Variable | Required | Why the Mini Apps need it |
|---|---|---|
CHANNEL_AUTH_ISSUER_URL | Production; enables the /auth/* routes everywhere | The API's own public HTTPS origin, used as the OIDC issuer. Without it the Mini Apps cannot sign in |
CHANNEL_AUTH_TELEGRAM_APPS_JSON | Production | Maps each app ID to its bot: {"client":{"role":"client","botToken":"…","webhookSecret":"…"},"professional":{"role":"professional",…}}. The bot token verifies launch data; the webhook secret authenticates bot updates |
CHANNEL_AUTH_TELEGRAM_MAX_AGE_SECONDS | No (default 3600) | Oldest launch data accepted at sign-in |
CHANNEL_AUTH_ASSERTION_PRIVATE_KEY | When enabled | RSA (≥ 2048-bit) PKCS#8 PEM that signs the identity assertion |
CHANNEL_AUTH_ASSERTION_RETIRED_PUBLIC_KEYS_JSON | No | Old public keys still published during a rotation |
CHANNEL_AUTH_ASSERTION_AUDIENCE | When enabled | Client ID of the custom OIDC provider in Supabase Auth |
CHANNEL_AUTH_SUPABASE_PROVIDER | When enabled | Provider ID in Supabase Auth, such as custom:bookygram-channels |
SUPABASE_URL | Yes | Supabase project; the API calls its token, refresh and logout endpoints |
SUPABASE_PUBLISHABLE_KEY | When enabled | Sent as apikey to Supabase Auth. Never the secret key |
CLIENT_APP_ORIGIN | Production | HTTPS origin hosting the client Mini App. Allowed by CORS in production |
PROFESSIONAL_APP_ORIGIN | Production | HTTPS origin hosting the professional Mini App. Allowed by CORS in production |
CORS_ORIGIN | Production | Website origin, allowed alongside the two Mini App origins |
CLIENT_MANAGEMENT_LINK_KEYS_JSON | Production | Version-to-secret map for personal booking management links that the client app claims |
Outside production the API allows any CORS origin, so the app origins are optional during development. App origins must be exact HTTPS origins with no path or trailing slash.
Telegram settings
These are not environment variables, but sign-in fails without them:
- Mini App URL — in BotFather, point each bot's Mini App (or menu button) at the HTTPS origin that serves that app's build.
- Webhook — set each bot's webhook to
https://<api-origin>/webhooks/telegram/<app-id>withsecret_tokenequal to that app'swebhookSecret. Contact sharing and bot permission reach the API only this way. See Move Telegram webhooks to the API. - One consumer per bot — a bot with a webhook cannot also be polled by the local launchers.
Supabase Auth settings
Supabase Auth must have a custom OIDC provider whose issuer is
CHANNEL_AUTH_ISSUER_URL, whose client ID is CHANNEL_AUTH_ASSERTION_AUDIENCE,
and whose identifier is CHANNEL_AUTH_SUPABASE_PROVIDER. The API workspace's
npm run auth:provider script registers it using the operator-only
SUPABASE_SECRET_KEY, which never belongs in the API runtime. See
Mini App OIDC flow.
Example: local development with one phone
- Run the API on
127.0.0.1:3000with channel authentication configured. The issuer must be HTTPS even locally, so put the API behind a tunnel or TLS proxy and use that origin asCHANNEL_AUTH_ISSUER_URL. - Professional app: leave
VITE_API_URLempty, runnpm run dev, and tunnel port5173. The proxy forwards/authand/professionalto the API. - Client app: set
VITE_API_URLto the API's public HTTPS origin, runnpm run dev, and tunnel port4178. - Point each bot's Mini App URL at its tunnel, and each webhook at the API.
Expected result: opening either bot's Mini App shows real account data with no sample-data banner. A banner means the page received no launch data.