> ## Documentation Index
> Fetch the complete documentation index at: https://docs.catenatelematics.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Events and outcomes

> The six Connect embed SDK callbacks, their payloads, and what each one means

These are the callbacks you pass to [`open()`](/get-started/connect-sdk/javascript-reference#open-options), and three of them to [`resume()`](/get-started/connect-sdk/javascript-reference#resume-options). All six are optional. If you supply none, the flow still works and you learn the outcome from your [webhooks](/api-reference/webhooks/overview).

| Callback | Meaning | Payload |
| - | - | - |
| `onOpen` | The flow is ready. It can arrive more than once. | none |
| `onConnection` | One provider was connected. The flow is still running. | `{ connectionId: string }` |
| `onConnectionDeleted` | The fleet deleted a connection. The flow is still running. | `{ connectionId: string }` |
| `onSuccess` | The user finished and chose to come back to you. | `{ connectionIds: string[] }` |
| `onExit` | The flow ended without success. | `{ reason: string }` |
| `onClose` | The surface should be dismissed. | none |

`onSuccess` and `onExit` are mutually exclusive. Whichever arrives first is the outcome. `onClose` arrives right after either. 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. See [The package never dismisses your surface](#the-package-never-dismisses-your-surface). Nothing inside the flow produces it alone.

`onOpen` can arrive more than once, for example after the embedded page reloads, so make the handler safe to run twice.

If no callback arrives at all, [When you receive nothing](/get-started/connect-sdk/troubleshooting#when-you-receive-nothing) lists the cases. Each is a real outcome, not an error.

A launch has one ending. If you keep the surface up after `onSuccess` and the user connects another provider, you get `onConnection` for it but no second `onSuccess`. Call `open()` again if you want a fresh outcome.

## onSuccess means the user came back

<Warning>
  **`onSuccess` means the user came back, not that a connection was made.** It fires when the user presses the button on the success screen (**Done** in the iframe; in a popup or tab, **Return to** followed by your name as configured in Catena), which happens after the connection already exists. A user who connects and then closes the surface, or just leaves the tab, never produces `onSuccess`, and their connection is still real.
</Warning>

Treat `onSuccess` as "they are done and heading back to you", and treat your connection webhooks as the record of what was actually established. `connectionIds` lists the connections this session made, less any the fleet deleted before finishing, and is best effort for the same reason.

## onConnection is the connection being made

It fires as each provider is connected, while the flow is still running. Do not dismiss your surface on it, because the user may be about to add another provider. It exists mainly for the iframe mode, where the user is already looking at your page and may never press the button that produces `onSuccess`.

The same `connectionId` can arrive twice if a user re-authorizes a provider twice in one session. As with `connectionIds`, treat it as a prompt to check, and your webhooks as the record.

## onExit when the invite link never loaded

Two `reason` values mean the flow could not load the invitation at all. Both arrive straight after `onOpen`, followed by `onClose`. Behind them the flow shows an error screen, so if you take your surface down on `onClose` the user never sees it. Show your own message instead.

| `reason` | What happened | What you usually do |
| - | - | - |
| `invalid_invitation` | Catena does not recognize the invitation in the link. | Check the invitation you launched with. A new link is the fix, so retrying this one will not help. |
| `invitation_unavailable` | Catena could not read the invitation: the backend failed or took too long. | Offer a retry. The link may be fine. |

Every attempt to load an invite link counts as a view of that invitation, and your `invitation.viewed` webhook fires for each one. That includes an attempt that ends in `invitation_unavailable`, so a retry can produce a second webhook.

## onConnectionDeleted is a connection going away

The flow lets the fleet delete a connection, and this fires when it does, with the deleted connection's `connectionId`. Like `onConnection`, it is not an ending and the flow keeps running. If you stored an id from an earlier `onConnection`, drop it here instead of waiting for your webhooks to catch up. The connection may be one made before this session, so an id you have never seen is normal.

`onOpen`, `onConnection` and `onConnectionDeleted` do not fire on every path. The [redirect mode](/get-started/connect-sdk/how-it-works#the-three-modes) navigates away before the flow reports itself ready, and has no channel to deliver any of them.

## The package never dismisses your surface

The package never removes anything on its own: not the overlay, not the iframe and not the popup window. `onClose` means the flow is finished with the surface. Call `destroy()` on the handle `open()` returned, or tear down your own container or modal when it suits you.

The overlay has a close button in its top-right corner, and Escape does the same while focus is on the overlay's own controls. Escape pressed inside the flow goes to the flow's page, which the package cannot hear. Either one ends the launch and calls `onClose`. No `onSuccess` or `onExit` comes before it unless the flow had already sent one, and nothing comes after it. The flow comes off the overlay at once, but the overlay itself stays up until you call `destroy()`, so an `onClose` handler that does nothing leaves the user looking at an empty overlay.

The close button cannot be turned off. The overlay sits above everything on your page and takes the rest of it out of the tab order, so a control of your own could not be reached while it is up. If you want your own close control, or any other chrome around the flow, pass a [`container`](/get-started/connect-sdk/javascript-reference#open-options) and build the modal yourself. A container gets no close button and no Escape handling, so call `destroy()` from your own control.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.