Skip to main content

Embedded Partner Portal

Drop your partner’s real portal — their rooms, deals, resources, contacts, everything — straight into your own app, inside an iframe. The partner is signed in automatically with a short-lived token your backend mints, so there’s no second login. The frame auto-resizes to its content and reports navigation back to your page.
This is not the same as embedding lead forms. Lead forms are a public, unauthenticated capture surface loaded with a script tag and a form UUID. The partner portal is an authenticated product surface: it requires an SSO token minted by your backend, the @journeybee/embed SDK, and an allowlisted host. The two are completely separate integrations.
Embedding requires HTTPS on both the host page and the portal. The portal session cookie is SameSite=None; Secure, which browsers refuse to set on insecure origins. Use a tunnel (e.g. ngrok) for local testing.
Prefer a running example? The Journeybee Embed Examples repo is a live playground + copy-paste snippets (forms + portal), including a backend token-mint reference — clone it, pnpm install, pnpm dev.

How it works

  1. Your backend authenticates the user (you already do this) and mints a short-lived, single-use SSO token signed with your embed signing key.
  2. The @journeybee/embed SDK mounts an iframe pointing at the portal, carrying your public company id and the token.
  3. The portal verifies the token server-side, establishes a session, and hands off to the partner’s portal — signed in, in embed mode.
  4. The SDK wires a postMessage bridge: auto-resize, navigation events, sign-out.
You don’t pick a page. The embed lands on the portal and the portal decides what to show: a partner with one partnership lands straight in it, with several gets a picker. The embed’s only job is to authorise your app and sign the user in — everything after that is the portal’s own logic. The tenant is identified by your public company id (companies.uuid), not by the URL the portal is served from. Embedding does not require a custom domain — it works on the shared portals.journeybee.io or your own custom domain, independently.

Prerequisites (one-time, in Journeybee)

  1. Enable the Embedded Portal module — Settings → Portal Settings.
  2. Copy your company id (the public companies.uuid shown there). You pass it to the SDK as company and set it as the token’s iss/aud.
  3. Add the hostname of the page that will host the iframe to Embedded Portal Domains (e.g. app.acme.com). The portal refuses to render in a frame whose ancestor isn’t allowlisted (frame-ancestors CSP).
  4. Create an Embed Signing Key under Embed Signing Keys. The secret (jb_embed_…) is shown once — store it as a server-side secret.
These are three independent settings. Embedded Portal Domains (who may iframe the portal) is not the same as the portal Custom Domain (a branded URL for top-level access) — embedding works with neither, either, or both.

Install

Published on npm — @journeybee/embed:

Quick start

1. Mint a token on your backend

Run this on your server, after you’ve authenticated the user. Never mint a token in the browser — anyone with the signing key can impersonate any user.
Only the public company id (companies.uuid) ever appears in the token or in client code. Journeybee’s internal numeric ids are never exposed.

2. Mount the embed on the client

That’s the whole integration. portalHost is just where the portal is served; company identifies which tenant — they’re independent.

The createPortalEmbed call

It returns a PortalEmbed:

Events

Subscribe with embed.on(type, listener):

React

Re-authentication

A token is single-use and short-lived; the resulting session lasts much longer. When the session is missing or expired, the embed emits portal:authRequired — mint a fresh token and re-mount:

Security model

  • No secrets cross the iframe boundary. portal:authenticated is a bare signal — never a token or session. The session lives in an HttpOnly cookie the portal sets itself.
  • Tokens are backend-minted only, short-lived (exp ≤ iat + 120s), single-use (jti replay-guarded), and audience/issuer-bound to your company id. The signing secret never reaches the browser.
  • Origin allowlisting is enforced server-side: the portal only frames inside hosts you’ve added to Embedded Portal Domains.
  • Public ids only. The token and all client code use the public companies.uuid; internal numeric ids are never exposed.
  • Storage Access. In browsers that partition third-party storage (e.g. Safari), the portal prompts once for Storage Access so its session cookie is delivered inside the iframe. The SDK marks the iframe allow="storage-access".

Troubleshooting