Skip to main content
Everything the package does goes through a URL and a message channel, so a page can do it by hand. You set a few parameters on the invitation URL, open it in a frame, a window or the current tab, and listen for the messages the flow posts as it goes. What the package adds on top:
  • the cookie probe, and the fallback to a window or a redirect
  • the one-launch-at-a-time guard
  • resume()
What a plain integration gives up covers what you handle yourself without them.

The launch URL

embed names the context the flow is opened in: iframe for a frame on your page, popup for a window you open, redirect for the current tab.
The flow picks its session cookie attributes from embed, so a value that does not match the context is not cosmetic. A mismatched value breaks the session cookie or the message channel. A frame sent popup or redirect gets a cookie the browser may not send back from a frame, so the flow can fail partway. A window sent iframe posts no messages.
Set the parameters on the invitation URL, which keeps whatever it already carries. Invitation URLs already have a query string (?invite=…, or ?tms_invite=… for a TMS invitation), so add the parameters to it rather than starting a second one. In script, URL.searchParams does that for you, and it also encodes a leading # in brand_color and brand_secondary:
The URL must be https, except on localhost, 127.0.0.1 and [::1], for the reasons the inviteUrl option gives. The flow ignores parameters it does not recognize. The Presentation options section describes what the six optional parameters do, and the same rules hold here:
  • The flow validates the values.
  • An invalid value falls back to its default on its own.
  • The four branding parameters apply only when the embed key resolves.
The HTML examples use https://connect.example.com/?invite=YOUR_INVITE_ID where your invitation URL goes, with the parameters joined to its query string by &. The script examples use inviteUrl and the launchUrl() helper from The launch URL.

A frame

A frame on your page, with embed=iframe. It needs no allow attribute, because the flow uses nothing a cross-origin frame is denied by default:
Do not add sandbox. The flow opens its own window for some providers’ sign-in, and a sandbox without allow-popups blocks it. A link, or a navigation, with embed=redirect. The flow runs in the tab. If your invitation has a success and a failure URL, the flow returns to one of them, and that is where you learn the outcome. Without them, the flow ends on its last screen and your webhooks are the record. See The redirect path.

A window

A window, with embed=popup, opened under the name catena-connect and without noopener. noopener would sever the channel the messages travel on. Reusing the name catena-connect lands a second window.open() on the first window, but reloads it and restarts the flow. To bring an open window back, check popup.closed and call popup.focus() instead.
Call window.open() synchronously inside the click handler. An await between the click and the call spends the activation, and the browser blocks the window. window.open() returns null when the browser refused, and some blockers return a window that is already closed. The package treats both as blocked and navigates the tab with embed=redirect, and the example does the same.
With no listener at all, your connection webhooks tell you the outcome, and on the redirect path so does the page the flow returns to, if your invitation has redirect URLs. For many sites that is a complete integration.

Reading the completion messages

A frame or a window posts a message to the page that opened it when the flow is ready, as each provider is connected or a connection is deleted, and when the user finishes. Every message has this shape, with the detail beside event and no wrapper around it:
The events mean what the callbacks of the same name mean, including the two exit reasons for an invite link that never loaded. The flow posts close straight after success or exit, never on its own. A user who closes your window produces nothing. If the surface stays up the user can press the button again, and the outcome and close are posted again. The package keeps only the first outcome, so a plain listener should too. open can arrive more than once, for example after the embedded page reloads, so make its handler safe to run twice. Check three things before you trust a message, on every message:
  1. event.origin is the connect origin, the origin of your invitation URL.
  2. event.source is the frame or window you opened.
  3. event.data.source is "catena-connect".
A listener that does exactly that:
For a window, compare event.source with what window.open() returned. The listener does not check version, for the same reason the package does not: it handles the events it recognizes in any version, so a version bump cannot silence it. open is the flow’s own message and reaches a registered origin only, so receiving it is also the sign your origin is registered, as it is for onOpen with the package. Ignore events, versions and fields you do not recognize. Additions arrive in minor releases, and Versioning and support says what is promised to stay. Which events arrive depends on how the URL was opened:

What a plain integration gives up

  • No cookie probe and no fallback. The package checks whether the browser will send the flow’s cookie in a frame, and falls back to a window or a redirect when it will not. A plain frame in a browser that refuses third-party cookies shows the flow failing to sign in instead. If you cannot vouch for the browsers, open a window with embed=popup or navigate the tab with embed=redirect.
  • No single-launch guard. Two frames run two flows. Opening a window under the name catena-connect each time lands a second window.open() on the first window, but reloads it and restarts the flow.
  • No de-duplication of the outcome. The outcome and close can be posted more than once. Keep the first outcome yourself.
  • No teardown of the window. The package closes it on destroy(). You close your own.
  • No resume(). Which destination page was reached says how the flow ended, and your webhooks carry the detail, as they do on the package’s redirect path. Without a record written before the navigation, though, a destination page cannot tell a return from the flow apart from a direct visit.
  • No shadow-root surface. You style and position the frame or the window, and you decide when to take it down.
Frame when you control the browsers, window when you do not, redirect when you need nothing on the page at all. The three modes the package moves between are all available to a plain integration, by choosing the embed value and the opening method yourself.