Skip to main content
Your app’s ui/ doesn’t run on its own page — it runs inside the merchant’s Sentralbee dashboard: a web iframe on desktop, a native WebView on mobile. That means two things the host has to tell you. Which theme the merchant is using, so you don’t flash white in a dark dashboard. Where the safe area is, so your buttons aren’t under a phone’s notch. And it means you need a way to prove which workspace is looking at you. The host hands all three over a small postMessage bridge. You don’t touch the bridge directly — you read it through three hooks from @sentralbee/app-sdk/react, and the same code works in the iframe and the WebView. The host injects a shim that routes messages to native, so you write it once.
That’s a complete embed. The rest of this page is what each hook does and why.

Tell the host you’re ready

Call ready() once, after your component mounts:
The host waits for this before it pushes the theme, safe-area, and session down to you. It’s how the bridge avoids a race — the host doesn’t send anything until your UI is actually listening. One line, and you never think about it again.

Match the merchant’s theme

useHostTheme() returns 'light' or 'dark', and it stays in sync: when the merchant flips their dashboard to dark mode, this re-renders with the new value. Before the host answers, it makes a sensible first guess from the ?theme= param and the OS setting, so you don’t flash the wrong colours on first paint.
You decide what “dark” looks like — the hook just tells you which one to render.

Respect the safe area

On a phone, part of the screen is taken up by the notch and the home indicator. useSafeArea() returns the insets the host wants you to keep clear, in pixels:
Apply them as padding on your root element. On the web they’re 0, so this costs you nothing there and does the right thing on mobile. If you’d rather drive your styling from CSS variables, push the numbers into custom properties yourself — the hook gives you plain values to do whatever you like with.

Get a session token, and call your own backend

useSessionToken() is the important one. It asks the host for a short-lived session token and gives you back its loading state:
The token is not for reading data on its own. It’s a Bearer credential for calls to your own app’s backend. Your UI calls /api/* on your own origin with the token attached, and your backend verifies it and derives the workspace from it:
This is the golden rule from Concepts & lifecycle showing up in the UI: the workspace comes from the verified token, never from the request. Your frontend never sends a workspace id, and your backend never reads one from the body — so a session can only ever act as the tenant it was minted for. Your backend then uses its stored per-workspace key to call Sentralbee; see Calling the API.
workspace is decoded straight from the token ({ id, name, domain }) so you can render “Acme Store” in your header without a round-trip. It’s for display only — never branch your authorization on it. Your backend does that from the verified token.

Why a URL token is safe in dev but not in production

In local dev and in the native WebView, the session arrives as a ?token= on the embed URL — that’s how sentralbee dev hands you a live session with no real platform. Inside the production dashboard iframe, that same ?token= param is ignored. The token there comes only from the host, over the bridge. That’s deliberate. A framed embed that trusted a URL token could be handed a forged one by whatever page linked to it (session fixation). So the SDK accepts a URL token only when your app is top-level — never when it’s framed. You get the convenience in dev and the safety in production, with no code change on your side.
Serve your built UI (ui/dist) from your own origin — the starter does this in api/src/app.ts. The bridge only trusts host messages from the parent frame, and this keeps your embed and its /api/* on the same origin so the session token flows cleanly.

Where to next