Connect Events
The hosted Connect page streams a stable, namespaced connect/* event vocabulary to your onEvent handler through the Elements SDK. Use it to build a Plaid-parity conversion funnel — see exactly which pane a user reached, why they dropped, and when the link is ready.
See also: Elements SDK guide · Webhooks (the authoritative outcome record) · Live Connect playground
onEvent is a UX-analytics stream, not a source of truth. It runs in the user's browser and can be lost to a closed tab. Confirm the linked outcome with the link.completed / link_token.completed webhook.
The onEvent handler
Register an onEvent callback on any flow. It receives the event name and a snake_case metadata object. The name is typed as the ConnectEventName union (with autocomplete) widened to allow any string, so narrowing on the name is how you get the typed metadata shape.
ConnectEventHandler
import type {
ConnectEventName,
ConnectEventMetadataByName,
} from 'billerapi-js';
// The SDK-facing onEvent signature (from billerapi-js):
type ConnectEventHandler = (
eventName: ConnectEventName | (string & Record<never, never>),
metadata: Record<string, unknown>,
) => void;
const onEvent: ConnectEventHandler = (eventName, metadata) => {
// Narrow on the name to get the typed metadata shape.
if (eventName === 'connect/authenticated') {
// metadata is a ConnectEventMetadataByName['connect/authenticated']
const m = metadata as ConnectEventMetadataByName['connect/authenticated'];
console.log('Login verified for', m.biller_id, 'at', m.timestamp);
return;
}
if (eventName === 'connect/view') {
const m = metadata as ConnectEventMetadataByName['connect/view'];
analytics.track('connect_view', { step: m.view_name });
return;
}
if (eventName === 'connect/error') {
const m = metadata as ConnectEventMetadataByName['connect/error'];
if (m.will_retry) return; // backgrounded transient failure — pending, not terminal
analytics.track('connect_error', { code: m.error_code });
return;
}
};
elements.connect({ linkToken: 'lt_xxx', onEvent, onSuccess, onExit }).open();Metadata envelope
Every connect/* event carries these base fields (all snake_case — the payload crosses the postMessage wire). Per-event tables below list only the fields added on top of this base.
ConnectEventMetadataBase
| Field | Type | Description |
|---|---|---|
| link_session_id* | string | null | Link token id, used as the session key for v1. `null` before INIT. |
| timestamp* | string | ISO-8601 emission timestamp. |
| biller_id | string | Selected biller id, once known. |
| biller_name | string | Selected biller name, once known. |
| view_name | ConnectViewName | The pane the event was emitted from / about. |
| error_code | ConnectErrorCode | Machine-readable error code (present on `connect/error`). |
| mode | ConnectFlowMode | The flow mode — `'connect'` (first-time) or `'update'` (repair). Present once the flow's mode is known. |
Flow mode — ConnectFlowMode
The mode field is present on every event once the flow's mode is known. It is one of two values:
| Value | Meaning |
|---|---|
connect | A first-time connect: the user picks a biller, consents, and links accounts for the first time. |
update | A repair / re-authentication of an EXISTING Link (stale credentials, expired session, MFA again, lapsed consent). The biller is already known, consent is skipped, and the account step re-confirms the existing selection. connect/open with mode: 'update' marks an update flow starting. |
Panes — ConnectViewName
The pane/step the user is on, in flow order. Fired as view_name on connect/view (and echoed on most other events), and as status on connect/exit.
| view_name | Pane |
|---|---|
loading | Initial/processing pane before the first interactive step. |
biller_selection | The biller picker. |
consent | The data-sharing consent screen (connect flows only). |
region | Region / locale disambiguation, when a biller needs it. |
credentials | The credentials entry form. |
mfa | An MFA / OTP challenge. |
account_selection | Selecting which discovered accounts to link. |
success | Terminal success pane. |
failure | Terminal failure pane. |
Error codes — ConnectErrorCode
Carried as error_code on connect/error. These are a fixed, machine-readable vocabulary derived from the underlying wire reason — never from localized UI copy — so you can key analytics and error handling off them safely.
| error_code | Meaning |
|---|---|
credentials_rejected | The biller rejected the submitted credentials. |
mfa_invalid | The submitted OTP / MFA code was wrong. |
mfa_expired | The OTP / MFA code (or challenge) timed out or expired. |
mfa_locked_out | Too many MFA attempts; the account is locked. |
account_discovery_failed | Login succeeded but the account list couldn't be read. |
connection_lost | The realtime stream dropped and couldn't reconnect. |
link_expired | The link token is no longer valid. |
internal_error | A generic client/network or unclassified backend fault. |
Event names — ConnectEventName
| Event | Fires when |
|---|---|
| connect/open | The hosted page initialized after its INIT handshake. The first event of every flow. For an update flow, this carries `mode: "update"`. |
| connect/view | Fires on every pane/step transition. The single most useful funnel signal — pipe `view_name` straight into your analytics to see where users drop. |
| connect/search | A settled (debounced) biller search query. Debounced ~400ms so you get the query the user paused on, not every keystroke. |
| connect/biller_selected | A biller was chosen from the picker. Carries the selected `biller_id` (and `biller_name` once resolved). |
| connect/credentials_submitted | Credentials were submitted for login. No credential values are ever included — this is a funnel signal only. |
| connect/mfa_required | An MFA challenge is now required. Carries `mfa_type` when the biller reports the channel. |
| connect/otp_submitted | An OTP / MFA code was submitted. Like `credentials_submitted`, no code value is included. |
| connect/input_required | The flow transitioned from a waiting/processing state to one needing user input. Carries the `view_name` now awaiting input — use it to re-focus your UX or nudge the user back. |
| connect/authenticated | The biller-side login was verified. Fires once per flow, as soon as the wire confirms authentication — usually well before accounts are discovered or the flow completes. A great signal to flip your UI to "connecting your accounts…". |
| connect/backgrounded | The user chose to continue the connect in the background: the backend auto-selects all discovered accounts and completes server-side. Followed by a clean `connect/exit` (no error) — a backgrounded close is NOT a bail. Watch for the `link_token.completed` webhook to know when the link is ready. |
| connect/error | An error surfaced. `error_code` is always present and is drawn from the fixed ConnectErrorCode vocabulary (never localized copy). When `will_retry` is true, the backend is retrying a backgrounded transient failure — treat it as "pending", not terminal. |
| connect/exit | The user bailed (or a backgrounded flow closed). Carries `status` = the pane they exited from. Pair with `connect/backgrounded` to distinguish a genuine bail from a background handoff. |
| connect/handoff | Successful handoff of the public token to the parent. This is the interactive success signal — the SDK `onSuccess` callback fires alongside it. Still confirm the outcome with the `link.completed` webhook before fulfilling anything server-side. |
connect/open
The hosted page initialized after its INIT handshake. The first event of every flow. For an update flow, this carries `mode: "update"`.
No fields beyond the base envelope. Metadata type: ConnectEventMetadataBase.
connect/open metadata
handler.onEvent = (eventName, metadata) => {
// eventName === 'connect/open'
// metadata: { link_session_id, timestamp, mode? }
};connect/view
Fires on every pane/step transition. The single most useful funnel signal — pipe `view_name` straight into your analytics to see where users drop.
ConnectViewEventMetadata — added fields
| Field | Type | Description |
|---|---|---|
| view_name* | ConnectViewName | The pane the user just landed on. Required on this event. |
connect/view metadata
// { link_session_id: 'lt_…', view_name: 'credentials', timestamp: '…' }connect/search
A settled (debounced) biller search query. Debounced ~400ms so you get the query the user paused on, not every keystroke.
ConnectSearchEventMetadata — added fields
| Field | Type | Description |
|---|---|---|
| query* | string | The settled search string. |
connect/search metadata
// { link_session_id: 'lt_…', query: 'pacific gas', timestamp: '…' }connect/biller_selected
A biller was chosen from the picker. Carries the selected `biller_id` (and `biller_name` once resolved).
ConnectBillerSelectedEventMetadata — added fields
| Field | Type | Description |
|---|---|---|
| biller_id* | string | The biller the user selected. Required on this event. |
connect/biller_selected metadata
// { link_session_id: 'lt_…', biller_id: 'test_electric_company', biller_name: 'Test Electric Company', timestamp: '…' }connect/credentials_submitted
Credentials were submitted for login. No credential values are ever included — this is a funnel signal only.
No fields beyond the base envelope. Metadata type: ConnectEventMetadataBase.
connect/credentials_submitted metadata
// { link_session_id: 'lt_…', biller_id: 'test_electric_company', timestamp: '…' }connect/mfa_required
An MFA challenge is now required. Carries `mfa_type` when the biller reports the channel.
ConnectMfaRequiredEventMetadata — added fields
| Field | Type | Description |
|---|---|---|
| mfa_type | string | The MFA channel (e.g. `sms`, `email`, `app`), when the biller reports one. |
connect/mfa_required metadata
// { link_session_id: 'lt_…', view_name: 'mfa', mfa_type: 'sms', timestamp: '…' }connect/otp_submitted
An OTP / MFA code was submitted. Like `credentials_submitted`, no code value is included.
No fields beyond the base envelope. Metadata type: ConnectEventMetadataBase.
connect/otp_submitted metadata
// { link_session_id: 'lt_…', view_name: 'mfa', timestamp: '…' }connect/input_required
The flow transitioned from a waiting/processing state to one needing user input. Carries the `view_name` now awaiting input — use it to re-focus your UX or nudge the user back.
ConnectViewEventMetadata — added fields
| Field | Type | Description |
|---|---|---|
| view_name* | ConnectViewName | The pane now awaiting user input. Required on this event. |
connect/input_required metadata
// { link_session_id: 'lt_…', view_name: 'mfa', timestamp: '…' }connect/authenticated
The biller-side login was verified. Fires once per flow, as soon as the wire confirms authentication — usually well before accounts are discovered or the flow completes. A great signal to flip your UI to "connecting your accounts…".
No fields beyond the base envelope. Metadata type: ConnectEventMetadataBase.
connect/authenticated metadata
// { link_session_id: 'lt_…', biller_id: 'test_electric_company', timestamp: '…' }connect/backgrounded
The user chose to continue the connect in the background: the backend auto-selects all discovered accounts and completes server-side. Followed by a clean `connect/exit` (no error) — a backgrounded close is NOT a bail. Watch for the `link_token.completed` webhook to know when the link is ready.
ConnectViewEventMetadata — added fields
| Field | Type | Description |
|---|---|---|
| view_name* | ConnectViewName | The pane the "continue in background" choice was made from. Required on this event. |
A backgrounded flow completes server-side. The public token is never exposed on this path — do not wait for onSuccess; treat the link_token.completed webhook as the completion signal.
connect/backgrounded metadata
// { link_session_id: 'lt_…', view_name: 'account_selection', timestamp: '…' }connect/error
An error surfaced. `error_code` is always present and is drawn from the fixed ConnectErrorCode vocabulary (never localized copy). When `will_retry` is true, the backend is retrying a backgrounded transient failure — treat it as "pending", not terminal.
ConnectErrorEventMetadata — added fields
| Field | Type | Description |
|---|---|---|
| error_code* | ConnectErrorCode | Machine-readable error code. Required on this event. |
| will_retry | boolean | Present (true) when the failure is a backgrounded transient one the backend retries on its own. Treat as pending, not terminal. |
connect/error metadata
// { link_session_id: 'lt_…', error_code: 'credentials_rejected', view_name: 'credentials', timestamp: '…' }connect/exit
The user bailed (or a backgrounded flow closed). Carries `status` = the pane they exited from. Pair with `connect/backgrounded` to distinguish a genuine bail from a background handoff.
ConnectExitEventMetadata — added fields
| Field | Type | Description |
|---|---|---|
| status* | ConnectViewName | The pane the user exited from. Required on this event. |
connect/exit metadata
// { link_session_id: 'lt_…', status: 'credentials', timestamp: '…' }connect/handoff
Successful handoff of the public token to the parent. This is the interactive success signal — the SDK `onSuccess` callback fires alongside it. Still confirm the outcome with the `link.completed` webhook before fulfilling anything server-side.
No fields beyond the base envelope. Metadata type: ConnectEventMetadataBase.
connect/handoff metadata
// { link_session_id: 'lt_…', biller_id: 'test_electric_company', timestamp: '…' }