@catenaclearing/connect-sdk exports three functions: open, resume and preload.
open(options)
Launches the flow. A click handler is the usual place to call it, but the iframe needs no user gesture: the popup takes its gesture from the Continue button.
Returns
{ destroy(): void }. See destroy().
open() throws if it is called outside a browser, or before <body> exists with no container.
string
required
The invitation URL. The origin the package trusts is derived from this URL. That origin is used for the iframe, the popup, and every inbound message check. The package has no origin of its own, so pointing it at a development environment is just a different invite URL. No separate build, no configuration.The URL must be
https, except on localhost, 127.0.0.1 and [::1]. Over plain http on a shared network, anyone on that network can be the origin: they can serve the flow, post a fake success, and read the embed key.Pass only invite URLs you created, in the Catena dashboard or through the Catena API, and hand them to the page from your own server. Never take one from your page’s query string, a form field, or anything else a visitor or another site can set. Whoever chooses the URL chooses the origin the package trusts, and can do the same three things.open() throws a TypeError if the URL does not parse, is not http or https, or is http on any host other than localhost, 127.0.0.1 or [::1].string
required
Your embed key, issued by Catena. It is valid only for the origins registered against it. See What you need.
HTMLElement
An element to mount the flow inline. Omit it and the package renders its own overlay. Behaviour is otherwise identical, including on the fallback modes, with one difference: the overlay has a close button and Escape handling, and a container has neither, so the way out of it is yours to provide. See The package never dismisses your surface. The package only removes what it created, so your container is left as it was found.The surface fills the container and is at least 480px tall. Give the container a height of its own, at least that much. In a shorter container the surface overflows it, and in one with no height set the surface is exactly 480px.
() => void
The flow is ready. It can arrive more than once, for example after the embedded page reloads, so make the handler safe to run twice. Not delivered in the redirect mode.
({ connectionId: string }) => void
One provider was connected. The flow is still running, so do not dismiss on it. Not delivered in the redirect mode.
({ connectionId: string }) => void
The fleet deleted a connection. The flow is still running. Drop the id if you stored it from
onConnection; it may also be a connection made before this session. Not delivered in the redirect mode.({ connectionIds: string[] }) => void
The user finished and chose to come back to you. Mutually exclusive with
onExit.({ reason: string }) => void
The flow ended without success. Mutually exclusive with
onSuccess. Two reasons mean the invite link never loaded: see onExit when the invite link never loaded.() => void
The surface should be dismissed. It arrives right after
onSuccess or onExit. It arrives on its own in two cases: the user closes the popup window in the popup mode (the package checks the window every half second), or the user presses the overlay’s close button or Escape. Nothing inside the flow produces it alone.Presentation options
variant, theme, brandColor, brandSecondary, logoUrl and logoUrlDark control how the flow looks. All six are optional. Omit them and the flow renders with the branding configured on your account.
"full" | "card" | string
default:"full"
Which layout the flow renders.
"full", the default, is the flow as it renders on its own: a background, a navigation header carrying the logo, a theme toggle, help and Disconnect, and the connection card with the “Secure connection powered by Catena” attribution below it."card"is the connection card alone, with the attribution moved inside it. It fills whatever it is mounted in edge to edge, so the card’s own background is all you see: no page background behind it and no border or rounded corners of its own. The surface around it supplies the frame. The header goes with everything else, so there is no theme toggle and no Disconnect. Help moves into the card, at the top right of each screen. The logo still appears on the invite consent card, which opens an invited session in either variant.
"card" is the fit for it and for a container you have already styled. The option rides on the launch URL, so it also applies when the flow falls back to a popup or a redirect, where it has a whole tab. "full" suits that, and a container with room for it.A user who needs to disconnect a provider needs the header. Use "full", or send them to the flow outside your page."light" | "dark" | string
Forces the color scheme.
"light" or "dark" overrides the user’s stored preference and the themes configured on your account, and the flow offers no theme toggle while one is set. The forced value is not stored, so the user’s own preference is untouched on their next visit to the flow outside your page.Omit it to leave the resolution alone: the user’s stored preference, then their device preference, then light.There is no "auto". The iframe is a separate document and cannot see your page’s theme. If you want the flow to match your surface, say which one you are in. If you want it to follow the user’s device, pass nothing.The package’s own surface (the loading state, the Continue button and the overlay’s close button) is dark under "dark" and light under "light". When you pass nothing it follows the device’s preference only. It cannot see a preference the user stored in the flow, so a user whose stored choice differs from their device sees the surface in one scheme and the flow in the other. Pass a theme if they must match.string
The flow’s primary color, in both light and dark: the progress indicator and the primary buttons. Six hex digits, with or without a leading
#, so "d1004b" and "#d1004b" are the same value. Text and icons drawn on the color are derived to stay readable, so a near-white or near-black value still produces legible buttons.The progress indicator shows this color until the user has an active connection, then switches to the success color: brandSecondary if you set one, the flow’s green if not. The buttons keep the brand color throughout.Anything else is ignored and the color configured on your account applies: three-digit shorthand, eight digits with alpha, named colors, rgb() and hsl().string
The flow’s success color, in both light and dark. It replaces the green that marks a connection: the progress indicator once the user has one, the border and marker on a connected provider, the “Connected” badges and the success messages. Lighter and darker shades for the badges and messages are derived from it.Same format as
brandColor, and the same values are ignored. It is independent of brandColor: omit it and the green stays, whatever brandColor is.A darker shade of it is used for small text and icons on the card, so a very pale color reads poorly there.string
Replaces your account’s logo wherever the flow shows one: the navigation header under
variant: "full", and the invite consent card that opens an invited session under either variant. With "card" there is no header, so the logo appears on the consent card alone.An absolute https URL on any host. Any other scheme, a protocol-relative URL or a relative path is ignored.The user’s browser fetches the image directly from the host you name. It is not proxied and not resized, though an oversized image is constrained to the logo’s slot. A URL that fails to load collapses the logo area rather than showing a broken image or falling back to your account’s logo.string
The logo shown in the dark theme, in the same places as
logoUrl. It falls back to logoUrl when omitted or invalid. The same rules apply: an absolute https URL on any host, fetched directly by the user’s browser, and a failed load collapses the logo area.Branding needs your embed key to resolve.
brandColor, brandSecondary, logoUrl and logoUrlDark are honored only when the launch’s embed key resolves to a registered, active key. With an unknown or revoked key all four are ignored and the flow uses the branding configured on your account. variant and theme need no key.destroy()
Removes everything the launch owns: its surface, the iframe or window it opened, its message listener and its timers. It is idempotent and safe to call after the flow has ended, so you can call it from a component teardown without checking state first. The one thing it leaves alone is a capability probe already in flight. The probe belongs to the page, not to the launch (preload() runs one with no launch at all). It cleans itself up within its own timeout, and its result is kept so the next launch with the same embed key skips the loading state.One flow at a time
Callingopen() while a launch is live brings it back to the user and returns that launch’s handle. It does not start a second flow. In the popup mode, once the window is open, that means focusing the window. Otherwise it focuses the overlay, and in a container it does nothing visible.
Once a launch has ended (after onSuccess, onExit or onClose) and you have not called destroy(), open() replaces it: the old launch gets onClose if it has not had one, its surface is removed, and a new flow starts with a new handle. If that onClose throws, the new flow still starts, and the error is reported from its own task, the way an error thrown from any other callback is.
resume(options)
Only relevant to the redirect mode, and only when your invitation has redirect URLs. Call it on the page the flow returns to.status is success on the success page. On the failure page it is failure when the user declined consent or disconnected, and integration-requested when they requested an integration or joined a waitlist, the ending that arrives as onExit in the other modes. skip_reason is added on a decline with a reason, and tsp_count on both.
It emits the terminal callback you declared (onSuccess for "success", onExit for "exit"), followed by onClose. Never onOpen: the flow reported itself ready on a page the navigation already destroyed.
onExit gets the same reason the other modes report for the same exit: the URL’s skip_reason when there is one, otherwise its status (for example failure or integration-requested). It is empty only if the URL carries neither. connectionIds is always empty, because nothing on the URL lists them. Read tsp_count from the query yourself if you need it. Your connection webhooks are the record, as they are on every path.
Both destination pages must be on the same origin as the page that called open(), or resume() emits nothing.
With no launch in flight, or an expired one, resume() does nothing, so it can sit at the top of a destination page unconditionally. The flip side: a callback means a redirected launch left this tab recently, not that this page was reached from the flow. An abandoned launch stays recorded for up to 30 minutes, and a resume() inside that window will consume it. Put the call on your two destination pages and nowhere else. Troubleshooting covers the cases where resume() emits nothing.
preload(options)
embedKey you will pass to open(), so the warmed check applies to the launch. Without it the check still runs, but open() repeats it with the key.
Runs the iframe check early so the next open() chooses its mode with no loading state. It is an optimization, not a requirement. open() behaves the same whether or not you call it; the only difference is a loading state about one network round trip long.
It cannot break your page. An invite URL the package cannot derive an origin from is ignored here. The same URL throws from open(), where a launch is at stake. Outside a browser, as in a server render, preload() does nothing, so it is safe to call from code that also runs on the server.
The result is held for the life of the page and never persisted. Browser settings change between visits, and a remembered “supported” would strand someone in an iframe their browser no longer allows.
Styling and isolation
The surface the package renders lives in a shadow root. Your global CSS cannot restyle the overlay, and the overlay’s CSS cannot leak into your page. The package’s own overlay is a modal dialog. It takes focus when it opens, marks the rest of the documentinert while it is up, and restores focus and the tab order on teardown. Anything you had already marked inert stays that way, so launching from inside your own modal gives your page back the way you had it. Mounted into a container you supplied, it does none of this. Your layout, your focus management.