Embedded apps
Render your app inside the merchant dashboard
Your app can render its own UI inside the Southbill merchant dashboard, so merchants never leave Southbill to configure or use it.
1. Enable the embedded surface
In your app detail page (Developer Dashboard → Apps → your app → Embedded app) set:
| Field | Notes |
|---|---|
embedded |
Turns the surface on |
embedded_url |
HTTPS only. The page Southbill loads in the iframe |
embedded_nav_label |
Optional label shown to the merchant (defaults to the app name) |
embedded_height |
Optional initial height, clamped to 400–4000 px |
Merchants then see an Open button next to your app in Dashboard → Apps, which opens /dashboard/apps/{installation_id}.
2. Bootstrap parameters
Southbill loads your embedded_url with:
?session_token=<JWT>&installation_id=insta_...&environment=test|live&host=https://southbill.com
Treat session_token as a one-time bootstrap value: exchange it for your own session server-side and drop it from the URL. Never store it in logs.
3. Where to find your App ID (aud) and signing secret
Both live in the same place: Developer Dashboard → Apps → your app → step OAuth & webhooks → card Embedded app.
| Value | Where | Looks like |
|---|---|---|
App ID (aud) |
Embedded app card, field App ID (aud) (copy button) | app_531ecbddea174f1d87738613d999577f |
| Session token signing secret | Embedded app card, Reveal button | random high-entropy string |
OAuth client_id / client_secret |
OAuth credentials card — different values, used for the OAuth install flow, never for token verification | sbc_… |
The value shown as App ID in the app Overview header is only a human-readable app number — it is not the aud value.
4. Verify the session token
The token is an HS256 JWT signed with your per-app signing secret (reveal it in the Embedded app card). It is an identity assertion only — it grants no API access.
{
"iss": "https://southbill.com",
"aud": "<your public app id>",
"sub": "<installation public id>",
"iat": 1730000000,
"nbf": 1729999995,
"exp": 1730000300,
"jti": "…",
"merchant_id": "…",
"merchant_name": "Acme GmbH",
"merchant_country": "DE",
"installation_id": "insta_…",
"environment": "test",
"scopes": ["store:read", "orders:write"]
}
Always verify server-side: signature, aud equals your app id, iss, exp/nbf, and that the installation belongs to the merchant you are about to act on. Lifetime is 300 seconds.
To call the Southbill API, keep using the OAuth access token you received at install time — the session token is not accepted by the API.
5. Southbill App Bridge (recommended)
Full reference: Southbill App Bridge — every function, the wire protocol, the security model and error list.
The Southbill App Bridge is the official and recommended way for an embedded app to talk to the surrounding dashboard. Your app stays sandboxed in its iframe; anything that belongs to the Southbill host — navigation, external pages, OAuth, toasts, modals, resizing, session tokens — goes through the Bridge.
<script src="https://www.southbill.com/southbill-app-bridge.js"></script>
const bridge = SouthbillAppBridge.create(); // reads ?host= automatically
await bridge.ready();
| Function | Purpose |
|---|---|
bridge.ready() |
Resolves once the host announced itself (southbill:ready), with the app context |
bridge.context() |
Safe context: app_id, app_name, installation_id, environment, locale, host. Never secrets |
bridge.sessionToken() |
Fresh short-lived (5 min) embedded session token |
bridge.resize(px) / bridge.autoResize() |
Iframe height, clamped to 400–4000 px |
bridge.toast(msg, variant) |
Native Southbill toast (success | error | info, max 300 chars) |
bridge.modal({ title, message, confirmLabel, cancelLabel, variant }) |
Host-level confirm modal, resolves true/false |
bridge.navigate("/dashboard/...") |
Internal Southbill navigation; other paths are rejected |
bridge.open(url) |
Opens an https page in a new browser tab |
bridge.redirect(url) |
Full top-level redirect away from the dashboard |
bridge.authorize(url, mode) |
OAuth / external authorization at browser top level (popup default, or redirect) |
bridge.close() |
Leaves the embedded app and returns to Dashboard → Apps |
External authorization
External OAuth or third-party consent pages must never be loaded inside the embedded iframe — most providers block framing and the sandbox forbids top-level navigation from your page. Request it through the Bridge instead:
await bridge.authorize("https://accounts.example.com/oauth/authorize?client_id=…&redirect_uri=…");
Southbill performs the navigation at the browser top level and your own callback URL finishes the flow on your backend.
Wire protocol (raw postMessage, backwards compatible)
The SDK is a thin wrapper. Requests are { type: "southbill:<action>", id?, ...payload } posted to the host origin; the host answers southbill:<action>:result or southbill:<action>:error with the same id. Supported actions: ping, context, session-token, resize, toast, modal, navigate, open, redirect, authorize, close. The legacy id-less messages (southbill:resize, southbill:toast, southbill:navigate, southbill:session-token) keep working unchanged.
const host = new URLSearchParams(location.search).get("host");
parent.postMessage({ type: "southbill:resize", height: document.body.scrollHeight }, host);
Security
Southbill validates the exact iframe window and the exact embedded_url origin of every message, ignores unknown actions and malformed payloads, restricts navigate to /dashboard/*, requires https for every external URL, and never returns Southbill secrets, API keys or the merchant login to your app. The only credential the Bridge hands out is the short-lived session token.
6. Requirements
- HTTPS with a valid certificate; the page must be frameable by Southbill (do not send
X-Frame-Options: DENY; useContent-Security-Policy: frame-ancestors https://southbill.com). - The iframe is sandboxed. Third-party cookies are unreliable — keep state on your backend keyed by
installation_id. - Handle
environmentexplicitly: sandbox installs must never touch live data. - Revoked or uninstalled installations stop receiving tokens (
409 installation_inactive); fail gracefully.