# 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. 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.

```json
{
  "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.

## 4. The postMessage bridge

Southbill only accepts messages from your exact `embedded_url` origin, and only these types:

| Message | Payload | Effect |
| --- | --- | --- |
| `southbill:resize` | `{ height: number }` | Resizes the iframe (400–4000 px) |
| `southbill:toast` | `{ message: string, variant?: "error" }` | Shows a toast (max 200 chars) |
| `southbill:navigate` | `{ path: "/dashboard/..." }` | Navigates the dashboard; other paths are ignored |
| `southbill:session-token` | – | Issues a fresh token, answered with `southbill:session-token:result` or `southbill:session-token:error` |

```js
const host = new URLSearchParams(location.search).get("host");
parent.postMessage({ type: "southbill:resize", height: document.body.scrollHeight }, host);
```

## 5. Requirements

- HTTPS with a valid certificate; the page must be frameable by Southbill (do **not** send `X-Frame-Options: DENY`; use `Content-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 `environment` explicitly: sandbox installs must never touch live data.
- Revoked or uninstalled installations stop receiving tokens (`409 installation_inactive`); fail gracefully.

