Skip to content

Reference

Events & callbacks

Per-open callbacks and the long-lived on / off event bus.

The Connect SDK exposes two parallel APIs for reacting to lifecycle events:

  • Per-open callbacks passed to open()onSuccess, onError, onExit, onEvent. These fire once. Their listeners are normally removed when the SDK runs close(); a throwing onSuccess, onError, or onExit callback prevents that final cleanup.
  • The event busalterConnect.on(event, handler) / alterConnect.off(event, handler). Listeners stay registered across multiple open() calls.

The two APIs receive the same success, error, and exit transitions. Do not depend on listener ordering: it depends on when each bus listener was registered relative to open(). onEvent is a separate per-open analytics callback and is not emitted as an event bus event.

Passed inside the OpenOptions object to open().

onSuccess: (grants: Grant[], completion: ConnectCompletion) => void

Fires when the popup flow finishes with at least one retained grant. The first argument is an array of Grant objects. The second is ConnectCompletion; its failedGrants array reports providers that authorized but were not retained because selected usage limits could not be applied.

onSuccess is the only required callback on open().

await alterConnect.open({
token: sessionToken,
onSuccess: (grants, completion) => {
for (const grant of grants) {
console.log(grant.provider, grant.grant_id);
}
console.log("not retained", completion.failedGrants);
},
});
onError?: (error: AlterError) => void

Fires when a failure reaches the browser SDK — for example, a blocked popup, redirect-state failure, malformed result, server-posted callback error, or total usage-limit application failure. The AlterError shape carries a machine-readable code, a message, optional details, and optional typed failedGrants.

An invalid or expired session detected while the hosted page loads is rendered there instead of being a reliable browser callback. Provider authorization denial returns to the hosted provider picker. Use backend session polling when the application must recover every terminal state.

onExit?: () => void

Fires when the user closes the popup window without finishing. Does not fire on the mobile redirect flow — there is no popup to close. Does not fire when the SDK closes the popup itself (after success or error). This is a local browser transition: it does not mark the backend session denied, so a server-side poll stays pending until timeout or session expiry.

onEvent?: (
eventName: string,
metadata: Record<string, unknown>,
) => void

Hook for analytics integrations. The SDK emits exactly one event today:

Event nameFires whenmetadata
connect_openedopen() has attempted to launch the Connect UI. It also fires after a blocked popup or failed redirect-state write.{ timestamp: string } — ISO 8601 timestamp.

For listeners that need to outlive a single open() call — typically when a long-lived component subscribes once at mount and unsubscribes at unmount — use the bus methods.

alterConnect.on(event: string, handler: (...args: never[]) => void): () => void
alterConnect.off(event: string, handler: (...args: never[]) => void): void

The variadic never[] is the package’s contravariant-safe declaration; concretely typed handlers in the table below are accepted. on() returns an unsubscribe function. Calling it is equivalent to calling off(event, handler).

The event emitter catches and logs exceptions from handlers, then continues to later handlers. The per-open onSuccess, onError, and onExit wrappers perform final SDK cleanup after application callback code returns, so a throwing callback prevents that final state/listener cleanup. Keep lifecycle handlers non-throwing. onEvent is different: it is called directly by open(), and an exception from it rejects the already-launched open() promise.

EventHandler signatureFires when
success(grants: Grant[], completion: ConnectCompletion) => voidPopup flow retained at least one grant. Same arguments as onSuccess.
error(error: AlterError) => voidA failure delivered to the browser SDK. Same shape as onError.
exit() => voidUser closed the popup without finishing. Same trigger as onExit.
close() => voidalterConnect.close() ran, either directly or after a non-throwing per-open success, error, or exit callback. destroy() does not emit it.

No event bus event is emitted. Analytics is available only through the per-open onEvent callback.

import { useEffect, useState } from "react";
import AlterConnect, {
type AlterError,
type ConnectCompletion,
type Grant,
} from "@alter-ai/connect";
const alterConnect = AlterConnect.create();
export function ConnectionStatus() {
const [latest, setLatest] = useState<Grant | null>(null);
useEffect(() => {
const unsubscribeSuccess = alterConnect.on(
"success",
(grants: Grant[], completion: ConnectCompletion) => {
setLatest(grants[0] ?? null);
console.log("not retained", completion.failedGrants);
},
);
const unsubscribeError = alterConnect.on("error", (err: AlterError) => {
console.error("connect failed", err);
});
return () => {
unsubscribeSuccess();
unsubscribeError();
};
}, []);
return latest ? <p>Connected: {latest.provider_name}</p> : null;
}

When to prefer the bus over per-open callbacks

Section titled “When to prefer the bus over per-open callbacks”
  • Cross-component state. Multiple components react to a successful connection — register a single on('success', ...) and dispatch from there.
  • Analytics middleware. A long-lived on('error', ...) handler can forward every failure to an error tracker without each open() site having to remember.

On a full-page redirect the browser leaves the page entirely and comes back to a fresh load, so the instance that called open() no longer exists. The new instance resolves the redirect outcome while it is being constructed — before create() has returned, and therefore before any application code can hold the instance to register a handler.

The outcome is retained for that reason. It is delivered to the first success or error listener registered, whenever that happens:

const alterConnect = AlterConnect.create();
// Registered after create(), and after any awaits — still receives the
// outcome of a redirect that completed before this line ran.
alterConnect.on("success", (grants, completion) => {
console.log("connected", grants);
});

It is delivered once. A later, unrelated on('success', ...) does not receive a repeat of an earlier redirect.

Report an issue with this page

Necessary

Required for sign-in, security, authorization, and remembering your choices.

Always active

Analytics

Helps us understand which product and documentation features are useful.

Performance diagnostics

Uses performance tracing and privacy-masked session replay to diagnose problems.

You can change these choices at any time from Cookie settings.