# Mono Connect.js
Source: https://sammydocs.mintlify.app/content/index
Link financial accounts or start payment flows with the Connect.js browser SDK.
Connect.js opens Mono's hosted Connect widget inside your web application. The widget handles institution selection, credentials, multi-factor authentication, account selection, and flow events. Your application controls when the widget opens and what happens after it succeeds.
This independent guide reflects `@mono.co/connect.js` 2.2.0. For production decisions, use Mono's [official financial data documentation](https://docs.mono.co/docs/financial-data/overview) and the [Connect.js source](https://github.com/withmono/connect.js).
## Choose a flow
Open the widget, receive an auth code, and exchange it for an account ID on your backend.
Reconnect a previously linked account by passing its Mono account ID to `reauthorise()`.
Configure the widget with the payments scope and payment data.
Follow browser-lifecycle examples for React, Angular, and Next.js.
## Install
```bash theme={null}
npm install @mono.co/connect.js
```
The package README lists Node.js 10 or later as its minimum requirement.
## Minimal account-linking example
The account-linking path requires a public key, `auth` scope, customer data, and a success callback. Call `setup()` before `open()`.
```javascript theme={null}
import Connect from "@mono.co/connect.js"
const connect = new Connect({
key: "PUBLIC_KEY",
scope: "auth",
data: {
customer: {
name: "Ada Lovelace",
email: "ada@example.com"
}
},
onSuccess: ({ code }) => {
// Send the short-lived code to your backend.
fetch("/api/mono/exchange", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code })
})
},
onClose: () => console.log("Connect closed")
})
connect.setup()
document
.querySelector("#link-account")
.addEventListener("click", () => connect.open())
```
## Keep the trust boundary clear
| Runs in the browser | Runs on your backend |
| --------------------------------------- | ---------------------------------- |
| Public key | Mono secret key |
| Connect.js widget | Auth-code exchange |
| Customer input passed to the widget | Account ID storage |
| `onSuccess` receives a short-lived code | Mono API requests for account data |
Never place `MONO_SECRET_KEY` in browser code, a public environment variable, or the Connect configuration. Send the auth code to your backend and exchange it there.
## SDK lifecycle
```text theme={null}
create Connect instance
↓
setup() or reauthorise(accountId)
↓
open()
↓
onEvent(eventName, data)
↓
onSuccess(result) or onClose()
```
For a copy-paste implementation including the backend exchange, continue to the [Quickstart](/content/quickstart). For compact facts intended for humans and agents, use the [Integration contract](/content/sdk/connect-js/reference/integration-contract).
## Version and evidence
See [Source and scope](/content/source-and-scope) for the exact package version, source commit, verification date, and precedence rules used by this guide.
# Quickstart: link an account
Source: https://sammydocs.mintlify.app/content/quickstart
Open Connect.js, receive an auth code, and exchange it for a Mono account ID on your backend.
Use this guide to open Connect.js, receive an auth code, and store the resulting Mono account ID from your backend.
## Before you start
You need:
* a Mono application
* its public key for the browser
* its secret key stored only on your backend
* Node.js 10 or later
Get application keys from the [Mono dashboard](https://app.mono.co/apps/).
## 1. Install Connect.js
```bash theme={null}
npm install @mono.co/connect.js
```
## 2. Create the widget
For a new customer, pass a name and email. If the customer already exists in Mono, pass `{ id: "CUSTOMER_ID" }` instead.
```javascript theme={null}
import Connect from "@mono.co/connect.js"
const connect = new Connect({
key: "PUBLIC_KEY",
scope: "auth",
data: {
customer: {
name: "Ada Lovelace",
email: "ada@example.com"
}
},
onSuccess: handleSuccess,
onClose: () => console.log("User closed Connect"),
onEvent: (eventName, data) => {
console.log(eventName, data)
}
})
connect.setup()
```
`setup()` mounts the widget but keeps it hidden. Create the instance once for the lifetime of the page or component.
## 3. Open the widget from a user action
```html theme={null}
```
```javascript theme={null}
document
.querySelector("#link-account")
.addEventListener("click", () => connect.open())
```
The user completes institution selection and authentication inside the hosted widget.
## 4. Send the auth code to your backend
After account linking, `onSuccess` receives an object containing `code`. Forward that code to your backend.
```javascript theme={null}
async function handleSuccess({ code }) {
const response = await fetch("/api/mono/exchange", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code })
})
if (!response.ok) {
throw new Error("Could not exchange Mono auth code")
}
const account = await response.json()
console.log("Linked Mono account:", account.id)
}
```
The auth code is an intermediate result. It is not the account ID and should not be used to reauthorize an account.
## 5. Exchange the code on your backend
The backend calls Mono's token-exchange endpoint with the secret key. The example below is framework-neutral server code.
```javascript theme={null}
export async function exchangeMonoCode(code) {
const response = await fetch("https://api.withmono.com/v2/accounts/auth", {
method: "POST",
headers: {
accept: "application/json",
"Content-Type": "application/json",
"mono-sec-key": process.env.MONO_SECRET_KEY
},
body: JSON.stringify({ code })
})
if (!response.ok) {
const error = await response.text()
throw new Error(`Mono token exchange failed: ${response.status} ${error}`)
}
return response.json()
}
```
Store the returned account ID against the correct user in your database. Follow Mono's [official exchange-token reference](https://docs.mono.co/api/bank-data/authorisation/exchange-token) for the current response schema and production requirements.
`MONO_SECRET_KEY` must never be prefixed with `NEXT_PUBLIC_`, embedded in a frontend bundle, logged, or sent to the browser.
## Confirm the integration
The first account-linking path is complete when:
1. `setup()` has mounted the hidden widget.
2. A user action calls `open()`.
3. `onSuccess` receives `{ code }` after linking.
4. Your backend exchanges the code.
5. Your application stores the returned Mono account ID.
## Continue
Adapt the lifecycle to React, Angular, or Next.js.
Instrument the widget lifecycle without treating events as success confirmation.
Reconnect a linked account with its account ID.
Diagnose setup, key, callback, and exchange failures.
# Angular
Source: https://sammydocs.mintlify.app/content/sdk/connect-js/frameworks/angular
Initialize Connect.js in an Angular component lifecycle and open it from the template.
Initialize Connect.js in `ngOnInit`, keep the instance on the component, and close it during teardown.
## Account-linking component
```typescript theme={null}
import { Component, Input, OnDestroy, OnInit } from "@angular/core"
import Connect from "@mono.co/connect.js"
@Component({
selector: "app-link-mono-account",
templateUrl: "./link-mono-account.component.html"
})
export class LinkMonoAccountComponent implements OnInit, OnDestroy {
@Input({ required: true }) customerId!: string
private connect: any
ready = false
ngOnInit() {
this.connect = new Connect({
key: "PUBLIC_KEY",
scope: "auth",
data: {
customer: { id: this.customerId }
},
onLoad: () => {
this.ready = true
},
onSuccess: ({ code }) => this.exchangeCode(code),
onClose: () => console.log("Connect closed")
})
this.connect.setup()
}
open() {
this.connect?.open()
}
ngOnDestroy() {
this.connect?.close()
}
private async exchangeCode(code: string) {
const response = await fetch("/api/mono/exchange", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code })
})
if (!response.ok) {
throw new Error("Could not complete Mono account linking")
}
}
}
```
The template opens the mounted widget only after `onLoad` marks it ready.
```html theme={null}
```
Load `PUBLIC_KEY` from Angular's browser-safe environment configuration. Do not add the Mono secret key to that configuration.
## Reauthorization
For an existing linked account, replace `setup()` with `reauthorise(accountId)`.
```typescript theme={null}
this.connect = new Connect({
key: "PUBLIC_KEY",
scope: "auth",
onSuccess: ({ code }) => this.handleReauthorization(code)
})
this.connect.reauthorise(this.accountId)
```
The argument is the Mono account ID string. It is not an auth code or reauth token.
## Angular-specific checks
* Initialize after Angular creates the component in the browser.
* Keep one SDK instance for the component lifetime.
* Tear down the mounted widget in `ngOnDestroy`.
* Route `onSuccess` data to a backend endpoint.
* Keep `MONO_SECRET_KEY` in the server environment only.
See [Basic account linking](/content/sdk/connect-js/usage/basic-integration) for the full lifecycle and [Troubleshooting](/content/sdk/connect-js/usage/troubleshooting) for common failures.
# Next.js
Source: https://sammydocs.mintlify.app/content/sdk/connect-js/frameworks/nextjs
Mount Connect.js in a Client Component and exchange auth codes in a server-only route.
Connect.js mounts an iframe and reads browser globals. Initialize the widget in a Client Component, then exchange the auth code in server-only code.
## Client component
```jsx theme={null}
"use client"
import { useEffect, useRef, useState } from "react"
export function LinkMonoAccount({ customerId }) {
const connectRef = useRef(null)
const [ready, setReady] = useState(false)
useEffect(() => {
let active = true
async function mountConnect() {
const { default: Connect } = await import("@mono.co/connect.js")
if (!active) return
const connect = new Connect({
key: process.env.NEXT_PUBLIC_MONO_PUBLIC_KEY,
scope: "auth",
data: {
customer: { id: customerId }
},
onLoad: () => setReady(true),
onSuccess: async ({ code }) => {
const response = await fetch("/api/mono/exchange", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code })
})
if (!response.ok) {
throw new Error("Could not complete Mono account linking")
}
}
})
connect.setup()
connectRef.current = connect
}
mountConnect()
return () => {
active = false
connectRef.current?.close()
connectRef.current = null
}
}, [customerId])
return (
)
}
```
The dynamic import keeps package loading inside the browser lifecycle. `NEXT_PUBLIC_MONO_PUBLIC_KEY` is intentionally public.
## Route handler
The Route Handler exchanges the short-lived code with the secret key. It must also authenticate the application user and store the returned account ID against that user.
```javascript theme={null}
// app/api/mono/exchange/route.js
import { NextResponse } from "next/server"
export async function POST(request) {
const { code } = await request.json()
if (typeof code !== "string" || code.length === 0) {
return NextResponse.json(
{ error: "Mono auth code is required" },
{ status: 400 }
)
}
const monoResponse = await fetch(
"https://api.withmono.com/v2/accounts/auth",
{
method: "POST",
headers: {
accept: "application/json",
"Content-Type": "application/json",
"mono-sec-key": process.env.MONO_SECRET_KEY
},
body: JSON.stringify({ code }),
cache: "no-store"
}
)
const result = await monoResponse.json()
if (!monoResponse.ok) {
return NextResponse.json(
{ error: "Mono token exchange failed" },
{ status: monoResponse.status }
)
}
// Persist result.id for the authenticated application user.
return NextResponse.json({ id: result.id })
}
```
Never name the secret `NEXT_PUBLIC_MONO_SECRET_KEY`. Variables with the `NEXT_PUBLIC_` prefix are included in client bundles.
## Reauthorization
Inside the Client Component, initialize with the existing account ID instead of calling `setup()`.
```javascript theme={null}
connect.reauthorise(accountId)
connectRef.current = connect
```
See [Reauthorization](/content/sdk/connect-js/usage/reauthorization) for the complete flow.
## Next.js-specific checks
* The component containing Connect.js begins with `"use client"`.
* Widget creation runs inside `useEffect`.
* The public key uses `NEXT_PUBLIC_`; the secret key never does.
* The Route Handler authenticates the application user before storing an account ID.
* Server errors returned to the browser do not contain the secret key or raw upstream response.
# React
Source: https://sammydocs.mintlify.app/content/sdk/connect-js/frameworks/react
Mount one Connect.js instance in a React component and send the auth code to a backend.
Create the Connect instance in an effect, keep it in a ref, and call `open()` from the button. This prevents the widget from being recreated on every render.
## Account-linking component
```jsx theme={null}
import { useEffect, useRef, useState } from "react"
import Connect from "@mono.co/connect.js"
export function LinkAccountButton({ customerId }) {
const connectRef = useRef(null)
const [ready, setReady] = useState(false)
useEffect(() => {
const connect = new Connect({
key: import.meta.env.VITE_MONO_PUBLIC_KEY,
scope: "auth",
data: {
customer: { id: customerId }
},
onLoad: () => setReady(true),
onSuccess: async ({ code }) => {
const response = await fetch("/api/mono/exchange", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code })
})
if (!response.ok) {
throw new Error("Could not complete Mono account linking")
}
},
onClose: () => console.log("Connect closed")
})
connect.setup()
connectRef.current = connect
return () => {
connect.close()
connectRef.current = null
}
}, [customerId])
return (
)
}
```
Replace the Vite environment-variable syntax if your build tool uses a different public-variable convention. Only the Mono public key belongs in the client bundle.
## Reauthorization
Use the stored Mono account ID and call `reauthorise()` instead of `setup()`.
```javascript theme={null}
useEffect(() => {
const connect = new Connect({
key: import.meta.env.VITE_MONO_PUBLIC_KEY,
scope: "auth",
onSuccess: ({ code }) => sendReauthorizationResult(code)
})
connect.reauthorise(accountId)
connectRef.current = connect
return () => connect.close()
}, [accountId])
```
## React-specific checks
* Initialize inside `useEffect`, where the DOM is available.
* Store the instance in `useRef`; do not store it in render state.
* Include values used to configure the instance in the effect dependency list.
* Close the instance when the component unmounts.
* Keep secret-key exchange logic in a backend route.
For the server request, follow the [Quickstart](/content/quickstart). For payment-specific response handling, see [Direct debit](/content/sdk/connect-js/usage/direct-debit).
# Events and callbacks
Source: https://sammydocs.mintlify.app/content/sdk/connect-js/reference/events
Observe Connect.js loading, user actions, errors, exits, and successful completion.
Connect.js exposes four callbacks. Each callback marks a different point in the widget lifecycle.
## Callback summary
| Callback | Required | Called when | Arguments |
| ----------- | -------- | ---------------------------------------------------------------- | ------------------- |
| `onSuccess` | Yes | Account linking or a payment flow completes. | One result object |
| `onClose` | No | The widget is hidden through a user exit, success, or `close()`. | None |
| `onLoad` | No | The widget iframe mounts and loads. | None |
| `onEvent` | No | A supported lifecycle or user-action event occurs. | `eventName`, `data` |
## `onSuccess`
Account linking returns an object containing a short-lived code.
```javascript theme={null}
onSuccess: ({ code }) => sendCodeToBackend(code)
```
A payment flow returns charge data instead. Keep flow-specific handlers separate so the code does not assume one response shape for both.
## `onClose`
```javascript theme={null}
onClose: () => {
setConnectOpen(false)
}
```
`close()` invokes this callback, including when the SDK closes after success.
## `onLoad`
Use `onLoad` to enable the trigger only after the widget is ready.
```javascript theme={null}
onLoad: () => {
setLinkButtonEnabled(true)
}
```
## `onEvent`
```javascript theme={null}
onEvent: (eventName, data) => {
console.log(eventName, data)
}
```
The current SDK maps widget messages to these event names:
| Event | Meaning |
| ---------------------- | ----------------------------------------------------------------------- |
| `LOADED` | The widget iframe loaded. |
| `OPENED` | The widget became visible. |
| `EXIT` | The user exited the flow. |
| `ERROR` | The widget reported an error. |
| `INSTITUTION_SELECTED` | The user selected an institution. |
| `AUTH_METHOD_SWITCHED` | The user changed authentication method. |
| `SUBMIT_CREDENTIALS` | The user submitted the institution login step. |
| `SUBMIT_MFA` | The user submitted a multi-factor authentication step. |
| `ACCOUNT_LINKED` | The widget linked an account. |
| `ACCOUNT_SELECTED` | The user selected an account. |
| `SUCCESS` | The SDK received its account-linked or charge-complete success message. |
## Payload handling
Payload fields depend on the event and may include an institution, authentication method, error metadata, page name, selected-account count, timestamp, or the optional `reference` supplied during setup.
Read event data defensively:
```javascript theme={null}
onEvent: (eventName, data = {}) => {
if (eventName === "INSTITUTION_SELECTED") {
analytics.track("mono_institution_selected", {
institutionId: data.institution?.id,
authMethod: data.authMethod
})
}
if (eventName === "ERROR") {
errorTracker.captureMessage("Mono Connect error", {
extra: {
errorType: data.errorType,
errorMessage: data.errorMessage,
reference: data.reference
}
})
}
}
```
Do not record credentials, MFA values, BVNs, account numbers, or unreviewed full event payloads. Define an allowlist of fields for analytics and logs.
## Success semantics
`ACCOUNT_LINKED` is useful telemetry about widget progress. Use `onSuccess` to obtain the auth code and start the backend exchange. Treat the backend's successful exchange and persistence as application-level completion.
# Integration contract
Source: https://sammydocs.mintlify.app/content/sdk/connect-js/reference/integration-contract
Canonical inputs, outputs, sequence, and trust boundaries for Connect.js 2.2.0.
This page is the compact integration contract for humans and coding agents. It describes the verified behavior of `@mono.co/connect.js` 2.2.0.
## Account-linking sequence
```text theme={null}
Browser Backend
------- -------
new Connect({ public key, auth scope, ... })
setup()
open()
user completes the hosted flow
onSuccess({ code })
─── POST { code } ────────────→ authenticated route
POST /v2/accounts/auth
header: mono-sec-key
store returned account ID
←── application response ──────
```
## Invariants
### Browser lifecycle
* `key` is required when constructing `Connect`.
* `onSuccess` is required when constructing `Connect`.
* Account linking should set `scope: "auth"` and provide `data.customer`.
* Call `setup()` before `open()` for new account linking and payment flows.
* Call `reauthorise(accountId)` before `open()` for reauthorization.
### Trust boundaries and outputs
* `reauthorise()` accepts an account ID string, not an auth code or reauth token.
* The public key may be used in browser code.
* The secret key must remain on the backend.
* An account-linking `onSuccess` result contains a short-lived code; the backend exchanges it for an account ID.
* Event payloads vary by event and should be read defensively.
## Constructor contract
```javascript theme={null}
new Connect({
key, // required string
onSuccess, // required function
onClose, // optional function
onLoad, // optional function
onEvent, // optional function
...config // forwarded to the hosted widget
})
```
Missing required values throw synchronously:
```text theme={null}
PUBLIC_KEY is required
onSuccess callback is required
```
## Method contract
| Method | Input | Effect | Return value |
| ------------------------ | -------------------------- | --------------------------------------------------------------------------- | ------------------- |
| `setup(config?)` | Optional setup object | Mounts a hidden widget for the configured flow. | No documented value |
| `reauthorise(accountId)` | Required account ID string | Mounts a hidden reauthorization widget. | No documented value |
| `open()` | None | Shows the mounted widget and begins listening for widget messages. | No documented value |
| `close()` | None | Hides the widget, removes the active message listener, and calls `onClose`. | No documented value |
| `fetchInstitutions()` | None | Requests current coverage data from `https://api.withmono.com/coverage`. | Axios promise |
## Flow outputs
| Flow | Browser success output | Required server action |
| --------------- | ----------------------------------- | ----------------------------------------------------------- |
| Account linking | `{ code }` | Exchange code for account ID with the secret key. |
| Reauthorization | Success data from the reauth widget | Complete the current Mono server-side reauthorization flow. |
| Payment | Charge data from the payment widget | Verify the payment server-side before fulfillment. |
## Source boundary
This contract covers the browser SDK implementation. Mono's official API documentation remains authoritative for server endpoint schemas, payment product behavior, data access, authentication policy, and production requirements.
# Methods
Source: https://sammydocs.mintlify.app/content/sdk/connect-js/reference/methods
Signatures and verified behavior for Connect.js 2.2.0 methods.
Connect.js exposes five public methods for mounting, opening, closing, and querying the widget integration.
## `setup(config?)`
Mounts the widget in the document and keeps it hidden until `open()` runs.
```javascript theme={null}
connect.setup()
```
Pass optional institution and account-match configuration to start further into an account-linking flow.
```javascript theme={null}
connect.setup({
selectedInstitution: {
id: "INSTITUTION_ID",
auth_method: "internet_banking",
account_number: "ACCOUNT_NUMBER"
},
check_account_match: true
})
```
| Field | Type | Description |
| ------------------------------------ | ------- | ------------------------------------------------ |
| `selectedInstitution.id` | string | Institution to load. |
| `selectedInstitution.auth_method` | string | Authentication method to preselect. |
| `selectedInstitution.account_number` | string | Account number used by account-match flows. |
| `check_account_match` | boolean | Requests account-number matching when supported. |
## `reauthorise(accountId)`
Mounts the reauthorization widget for an existing linked account. The widget remains hidden until `open()` runs.
```javascript theme={null}
connect.reauthorise("account_xyz")
```
| Parameter | Type | Required | Description |
| ----------- | ------ | -------- | --------------------------------------------------- |
| `accountId` | string | Yes | Mono account ID for the account being reauthorized. |
The method throws when the account ID is missing or is not a string. Use `reauthorise()` in place of `setup()` for a reauthorization instance.
## `open()`
Shows the widget mounted by `setup()` or `reauthorise()` and starts the window-message listener used for callbacks.
```javascript theme={null}
connect.setup()
connect.open()
```
Call `open()` from an explicit user action such as a button click.
## `close()`
Hides the widget, removes the active window-message listener, and invokes the configured `onClose` callback.
```javascript theme={null}
connect.close()
```
The SDK also closes the widget after a successful account-linking or charge-complete message.
## `fetchInstitutions()`
Requests institution coverage from `https://api.withmono.com/coverage` through Axios.
```javascript theme={null}
const response = await connect.fetchInstitutions()
console.log(response.data)
```
The method returns the Axios promise rather than only its `data` property. Handle network and non-success responses.
```javascript theme={null}
try {
const { data } = await connect.fetchInstitutions()
renderInstitutions(data)
} catch (error) {
showCoverageError(error)
}
```
## Complete lifecycle
```javascript theme={null}
import Connect from "@mono.co/connect.js"
const connect = new Connect({
key: "PUBLIC_KEY",
scope: "auth",
data: { customer: { id: "CUSTOMER_ID" } },
onSuccess: ({ code }) => sendCodeToBackend(code),
onClose: () => console.log("Connect closed")
})
connect.setup()
document
.querySelector("#link-account")
.addEventListener("click", () => connect.open())
```
# Parameters
Source: https://sammydocs.mintlify.app/content/sdk/connect-js/reference/parameters
Constructor, customer, payment, and setup parameters for Connect.js 2.2.0.
Pass constructor parameters to `new Connect(options)`. The SDK requires `key` and `onSuccess`; other values configure callbacks or are forwarded to the hosted widget.
## Constructor parameters
| Parameter | Type | Required | Purpose |
| ----------- | -------- | -------------- | ---------------------------------------------------------- |
| `key` | string | Yes | Mono public application key. |
| `onSuccess` | function | Yes | Receives the flow-specific success result. |
| `scope` | string | Flow-dependent | Use `auth` for linking or `payments` for payment examples. |
| `data` | object | Flow-dependent | Customer or payment configuration. |
| `onClose` | function | No | Runs when the widget closes. |
| `onLoad` | function | No | Runs after the widget iframe loads. |
| `onEvent` | function | No | Receives `eventName` and event-specific `data`. |
| `reference` | string | No | Correlation value included in supported event payloads. |
## Account-linking configuration
```javascript theme={null}
const connect = new Connect({
key: "PUBLIC_KEY",
scope: "auth",
data: {
customer: {
id: "CUSTOMER_ID"
}
},
reference: "APPLICATION_CORRELATION_ID",
onSuccess: ({ code }) => sendCodeToBackend(code),
onClose: () => console.log("Connect closed"),
onLoad: () => console.log("Connect loaded"),
onEvent: (eventName, data) => console.log(eventName, data)
})
```
### Customer
Identify an existing Mono customer with its ID:
```javascript theme={null}
data: {
customer: { id: "CUSTOMER_ID" }
}
```
Or supply fields for a new customer:
```javascript theme={null}
data: {
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
identity: {
type: "bvn",
number: "CUSTOMER_BVN"
}
}
}
```
Check official Mono documentation for current customer and identity requirements before production use.
## Payment configuration
Current package examples pass payment configuration inside `data`.
```javascript theme={null}
const connect = new Connect({
key: "PUBLIC_KEY",
scope: "payments",
data: {
type: "one-time-debit",
amount: 150000,
description: "Electricity bill"
},
onSuccess: (charge) => verifyChargeOnBackend(charge)
})
```
| Field | Example | Meaning |
| ------------------ | ------------------ | --------------------------------------------------- |
| `data.type` | `one-time-debit` | Payment flow type used by current package examples. |
| `data.amount` | `150000` | Integer amount in the currency's minor unit. |
| `data.description` | `Electricity bill` | User-visible payment description. |
Confirm current allowed types and required fields in Mono's official payment documentation.
## Setup configuration
Pass setup-only values to `setup(config)` rather than the constructor.
```javascript theme={null}
connect.setup({
selectedInstitution: {
id: "INSTITUTION_ID",
auth_method: "internet_banking",
account_number: "ACCOUNT_NUMBER"
},
check_account_match: true
})
```
## Secrets
The constructor accepts a public key. It does not require the Mono secret key.
Keep `MONO_SECRET_KEY` on your backend. Never pass it to `new Connect()`, expose it through a browser environment variable, or include it in frontend logs.
# Instrument account linking
Source: https://sammydocs.mintlify.app/content/sdk/connect-js/usage/basic-integration
Add lifecycle callbacks, safe diagnostics, and production checks to account linking.
Start with the [Quickstart](/content/quickstart) for the first working integration. This page adds customer handling, lifecycle callbacks, and safe diagnostics.
## Configure the browser flow
Create one Connect instance, call `setup()` once, and call `open()` from a user action.
```javascript theme={null}
import Connect from "@mono.co/connect.js"
const connect = new Connect({
key: "PUBLIC_KEY",
scope: "auth",
data: {
customer: {
name: "Ada Lovelace",
email: "ada@example.com"
}
},
reference: crypto.randomUUID(),
onSuccess: ({ code }) => sendCodeToBackend(code),
onLoad: () => setLinkButtonEnabled(true),
onClose: () => console.log("Connect closed"),
onEvent: (eventName, data) => recordConnectEvent(eventName, data)
})
connect.setup()
```
The required account-linking configuration is:
| Parameter | Purpose |
| --------------- | ------------------------------------------------------------------ |
| `key` | Identifies the Mono application in browser-safe configuration. |
| `scope: "auth"` | Selects the account-linking flow. |
| `data.customer` | Identifies an existing customer or supplies details for a new one. |
| `onSuccess` | Receives `{ code }` after successful linking. |
## Existing and new customers
Use the Mono customer ID when your backend already has one.
```javascript theme={null}
data: {
customer: { id: "CUSTOMER_ID" }
}
```
Supply customer details when creating a new customer in the flow.
```javascript theme={null}
data: {
customer: {
name: "Ada Lovelace",
email: "ada@example.com",
identity: {
type: "bvn",
number: "CUSTOMER_BVN"
}
}
}
```
Confirm current identity requirements in Mono's official documentation before collecting or sending sensitive identity data.
## Open and close
`setup()` mounts the hidden widget. `open()` makes it visible, and `close()` hides it programmatically.
```javascript theme={null}
document
.querySelector("#link-account")
.addEventListener("click", () => connect.open())
document
.querySelector("#cancel-linking")
.addEventListener("click", () => connect.close())
```
## Complete the link on your backend
The browser should send the short-lived code to an authenticated backend endpoint. The backend exchanges it with `MONO_SECRET_KEY`, verifies the result, and stores the account ID for the current application user.
```javascript theme={null}
async function sendCodeToBackend(code) {
const response = await fetch("/api/mono/exchange", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code })
})
if (!response.ok) {
throw new Error("Mono account linking could not be completed")
}
return response.json()
}
```
See the [Quickstart](/content/quickstart) for the corresponding backend exchange request.
## Instrument the flow
Use `onEvent` for product analytics and debugging. Use `onSuccess` as the browser-side success signal instead of relying on an intermediate event.
```javascript theme={null}
function recordConnectEvent(eventName, data) {
switch (eventName) {
case "OPENED":
analytics.track("mono_connect_opened")
break
case "INSTITUTION_SELECTED":
analytics.track("mono_institution_selected", {
institutionId: data?.institution?.id
})
break
case "ERROR":
errorTracker.captureMessage("Mono Connect error", {
extra: data
})
break
}
}
```
Event payloads vary by event. Read properties defensively and avoid logging credentials, identity values, or other sensitive user input.
## Completion checklist
* The widget uses the public key and `auth` scope, and `setup()` runs before `open()` from a user action.
* `onSuccess` sends the auth code to an authenticated backend endpoint.
* The secret key is present only on the backend.
* The returned account ID is associated with the correct application user.
* Event and error logs use an allowlist that excludes sensitive data.
# Direct debit
Source: https://sammydocs.mintlify.app/content/sdk/connect-js/usage/direct-debit
Configure Connect.js to launch a one-time or recurring payment flow.
Connect.js can launch payment flows by passing `scope: "payments"` and payment configuration in `data`.
Payment products and required fields can change independently of the JavaScript SDK. Confirm availability, field values, currency behavior, limits, and server-side verification in Mono's current official documentation before shipping a production flow.
## One-time debit
The current Connect.js examples use an integer amount in the currency's minor unit. In this naira example, `150000` represents ₦1,500.
```javascript theme={null}
import Connect from "@mono.co/connect.js"
const connect = new Connect({
key: "PUBLIC_KEY",
scope: "payments",
data: {
type: "one-time-debit",
amount: 150000,
description: "Electricity bill"
},
onSuccess: (charge) => {
// Send the returned reference to your backend for verification.
console.log("Payment flow completed", charge)
},
onClose: () => console.log("Payment flow closed")
})
connect.setup()
connect.open()
```
## Recurring debit
Use the recurring type shown in the current package examples when your Mono application and payment product support it.
```javascript theme={null}
const connect = new Connect({
key: "PUBLIC_KEY",
scope: "payments",
data: {
type: "recurring-debit",
amount: 150000,
description: "Monthly subscription"
},
onSuccess: handleCharge,
onEvent: (eventName, data) => console.log(eventName, data)
})
connect.setup()
```
## Account linking and payments return different results
| Flow | Configuration | `onSuccess` result |
| --------------- | ------------------- | ---------------------------------- |
| Account linking | `scope: "auth"` | Object containing an auth `code` |
| Payment | `scope: "payments"` | Charge data returned by the widget |
Do not destructure `{ code }` in a payment callback unless the current Mono payment contract explicitly returns that shape.
## Verify on the backend
Treat the browser callback as a signal to continue. It is not final proof that funds settled. Send the returned payment reference or charge identifier to your backend and verify it using the current Mono payment API before fulfilling an order or granting paid access.
## Avoid duplicate launches
Track whether a payment is already open or awaiting verification. Return early from repeated calls, and use the same state to disable the payment button in your interface.
```javascript theme={null}
let paymentPending = false
async function openPayment() {
if (paymentPending) return
paymentPending = true
connect.open()
}
async function handleCharge(charge) {
try {
await verifyChargeOnBackend(charge)
} finally {
paymentPending = false
}
}
```
# Reauthorization
Source: https://sammydocs.mintlify.app/content/sdk/connect-js/usage/reauthorization
Reconnect an existing Mono account by passing its account ID to reauthorise().
Reauthorization lets a user restore access to a previously linked account when the institution requires fresh authentication, multi-factor authentication, or another security step.
## Required input
`reauthorise()` accepts the existing Mono **account ID** as a string.
```javascript theme={null}
connect.reauthorise("account_xyz")
```
It does not accept the short-lived auth code returned by initial linking, and it does not accept a separate reauth token.
Older examples may refer to a reauth token. In `@mono.co/connect.js` 2.2.0, the implemented signature is `reauthorise(accountId: string)`.
## Implement the flow
Create the Connect instance, initialize it with `reauthorise(accountId)`, then open it from a user action.
```javascript theme={null}
import Connect from "@mono.co/connect.js"
const accountId = "account_xyz"
const connect = new Connect({
key: "PUBLIC_KEY",
scope: "auth",
onSuccess: ({ code }) => {
// Send the new code to your backend and complete the
// server-side flow required by Mono's current API.
return sendReauthorizationResult(code)
},
onClose: () => console.log("Reauthorization closed"),
onEvent: (eventName, data) => console.log(eventName, data)
})
connect.reauthorise(accountId)
document
.querySelector("#reauthorize-account")
.addEventListener("click", () => connect.open())
```
Use `reauthorise()` in place of `setup()` for this widget instance. If both methods are called, the last call replaces the mounted widget configuration.
## Where the account ID comes from
For a newly linked account:
1. `onSuccess` returns `{ code }` in the browser.
2. The browser sends the code to your backend.
3. The backend calls the [exchange-token endpoint](https://docs.mono.co/api/bank-data/authorisation/exchange-token) with the Mono secret key.
4. The response contains the account ID your application stores.
5. A later reauthorization passes that stored account ID to `reauthorise()`.
The account ID may also be available through the Mono dashboard or current customer/account APIs. Your backend should return only the identifier needed by the browser flow; it must not expose the Mono secret key.
## Handle validation errors
The SDK validates the `accountId` argument before mounting the widget.
```javascript theme={null}
function startReauthorization(accountId) {
if (typeof accountId !== "string" || accountId.length === 0) {
throw new Error("A Mono account ID is required")
}
connect.reauthorise(accountId)
connect.open()
}
```
The current SDK throws these errors:
| Condition | SDK error |
| --------------------------- | --------------------------------------------- |
| Missing or empty account ID | `Account ID is required for re-authorisation` |
| Non-string account ID | `Invalid accountId: must be a string` |
## Observe the result
Use `onEvent` to diagnose progress and `onSuccess` to handle completion.
```javascript theme={null}
onEvent: (eventName, data) => {
if (eventName === "SUBMIT_MFA") {
analytics.track("mono_reauth_mfa_submitted")
}
if (eventName === "ERROR") {
errorTracker.captureMessage("Mono reauthorization error", {
extra: data
})
}
}
```
Do not log MFA values, credentials, or identity fields.
# Troubleshooting
Source: https://sammydocs.mintlify.app/content/sdk/connect-js/usage/troubleshooting
Diagnose Connect.js setup, open, callback, reauthorization, and token-exchange failures.
Start with the observed symptom, then check the earliest lifecycle step that could produce it.
## The widget does not open
Check these conditions in order:
1. The code is running in a browser with `window` and `document` available.
2. `new Connect(...)` completed without throwing.
3. `setup()` or `reauthorise(accountId)` ran before `open()`.
4. `open()` runs from a user action such as a button click.
5. Browser policy, an extension, or a Content Security Policy is not blocking `https://connect.mono.co`.
In Next.js, initialize the SDK in a Client Component. Do not mount the widget during server rendering.
## Constructor errors
The current SDK throws synchronously when either required constructor value is missing.
| Error | Cause | Fix |
| -------------------------------- | ---------------------------- | ---------------------------------------------------------------------------------- |
| `PUBLIC_KEY is required` | `key` is empty or undefined. | Load the Mono public key in browser-safe configuration. |
| `onSuccess callback is required` | `onSuccess` was omitted. | Pass a function even if the first implementation only logs or forwards the result. |
## Reauthorization errors
`reauthorise()` requires a Mono account ID string.
| Error | Cause | Fix |
| --------------------------------------------- | --------------------------------------------- | ------------------------------------------------ |
| `Account ID is required for re-authorisation` | No account ID was passed. | Load the stored account ID for the current user. |
| `Invalid accountId: must be a string` | A number, object, or another type was passed. | Pass the account ID string returned by Mono. |
Do not pass the initial auth code or a value labeled `reauthToken`.
## `onSuccess` runs, but no account is stored
The browser callback returns a short-lived code. Your backend must still exchange it.
Check that:
* the browser sends `{ code }` to an authenticated backend route
* `MONO_SECRET_KEY` exists only in the server environment and is sent through `mono-sec-key`
* the backend parses the exchange response using Mono's current schema
* non-2xx responses are logged without exposing secrets
* the resulting account ID is stored against the correct application user
Use Mono's [official exchange-token reference](https://docs.mono.co/api/bank-data/authorisation/exchange-token) as the authority for the current request and response schema.
## Events appear more than once
Create one Connect instance per mounted page or component. Recreating the instance on every render can mount multiple widgets or register repeated listeners.
In React, create the instance inside `useEffect` and keep it in `useRef`. In Angular, initialize it in `ngOnInit`. In Next.js, keep initialization inside a Client Component.
## The wrong institution or account flow opens
Review the object passed to `setup()`:
```javascript theme={null}
connect.setup({
selectedInstitution: {
id: "INSTITUTION_ID",
auth_method: "internet_banking",
account_number: "ACCOUNT_NUMBER"
},
check_account_match: true
})
```
Remove `selectedInstitution` to return to the institution-selection screen. Confirm institution IDs and supported authentication methods against current coverage data.
## Collect a safe diagnostic record
Capture the SDK version, browser, flow type, event names, HTTP status codes, and your own correlation reference. Do not record credentials, MFA values, BVNs, account numbers, secret keys, or full event payloads that may contain sensitive data.
# Source and scope
Source: https://sammydocs.mintlify.app/content/source-and-scope
Version, evidence, and precedence rules for this independent Connect.js guide.
This is an independent documentation project for `@mono.co/connect.js`. It is not Mono's official documentation.
## Verification record
| Field | Value |
| --------------- | ------------------------------------------ |
| Package | `@mono.co/connect.js` |
| Package version | `2.2.0` |
| Source branch | `develop` |
| Source commit | `7fb3b7b36f8c57ea29c7baa55259bdacee4b28f8` |
| Last verified | July 22, 2026 |
The guide was checked against the public [Connect.js implementation](https://github.com/withmono/connect.js), its README, and Mono's official API documentation.
## Precedence
Use this order when sources disagree:
1. Mono's current [official documentation](https://docs.mono.co/docs/financial-data/overview) for production behavior and API policy.
2. The current [Connect.js source](https://github.com/withmono/connect.js) for SDK signatures and browser behavior.
3. This guide for explanation and implementation examples.
Payment capabilities, required customer fields, and API behavior can change independently of the JavaScript package. Verify production payment and financial-data integrations in Mono's official documentation.
## Documentation conventions
* `PUBLIC_KEY` means a Mono public key that may be used in browser code.
* `MONO_SECRET_KEY` means a secret that must remain on your backend.
* `code` means the short-lived value returned to `onSuccess` after account linking.
* `accountId` means the Mono account identifier returned after the backend exchanges that code.
* Examples use `scope: "auth"` explicitly for account linking.
* Reauthorization examples pass an `accountId` to `reauthorise()`. They do not pass a reauth token.
## Machine-readable access
Agents can discover the page index at [`/llms.txt`](https://sammydocs.mintlify.app/llms.txt), load the complete documentation from [`/llms-full.txt`](https://sammydocs.mintlify.app/llms-full.txt), and read the integration capability contract at [`/skill.md`](https://sammydocs.mintlify.app/skill.md). Individual Mintlify pages are also available as Markdown by adding `.md` to the page URL.