Connections live under a Client: Client → Connection → Account → Transaction / Statement. One Client can own many Connections (one per linked bank). See Connect a bank for the end-to-end setup, and Webhooks for the payloads referenced here.
The status flow
A connection has five statuses:| Status | Meaning | What you do |
|---|---|---|
initiated | You’ve kicked off the link; the widget URL isn’t ready yet. Brief and transient. | Nothing — poll the operation; you’ll see requires_action almost immediately. |
requires_action | The user still needs to finish in the widget. | Keep the widget open, or re-open the same widget_url. No auto-timeout. |
active | Linked and healthy. Accounts, transactions, and statements are flowing. | Read data. Watch capability and refresh events. |
failed | Bad credentials or an aggregator-reported failure. | Show a re-link CTA and re-initiate. |
disconnected | The user revoked access, or the link needs to be rebuilt. | Show a re-link CTA and re-initiate. |
From initiate to active
You never poll the connection directly during setup. You poll the operation you get back from the initiate call.Initiate the connection
POST /v3/clients/{id}/connections with the institution id returns 202 Accepted and an operation_id.Initiate
202 Accepted
There is no
source field. v3 routes to Finicity, MX, or FDE server-side from the institution catalog.Poll the operation until it succeeds
GET /v3/operations/{operation_id} until status is succeeded. The widget URL is at result.connection.action.widget_url.Operation succeeded
Hand off the widget
Open
widget_url in the user’s browser (redirect, iframe, or webview). They pick their bank, sign in, pick accounts, and close it. You never see or transmit credentials. See Connect a bank for widget details.The two id formats
This trips people up, so read it twice.- Placeholder —
con_<uuid>(e.g.con_9f1c2b7a-3e4d-4a11-8c2f-77e9b0d15a42). You get this while the connection isrequires_action. It is valid only for the widget step. - Canonical —
con_<SOURCE>_<bankAccountId>(e.g.con_FINICITY_41294,con_MX_1224). It appears once the user finishes andconnection.activefires. This is the only id that works on/accounts,/transactions, and/statements.
Handling requires_action
requires_action means the ball is in the user’s court. They opened the widget but have not finished picking accounts and signing in.
There is no auto-timeout to failed today. A connection stays requires_action until either:
- the user re-opens the still-valid
widget_urland completes it, or - you re-initiate the connection to get a fresh operation and widget URL.
POST /connections.
Handling failed and disconnected
Both statuses mean the same thing to your UI: the user needs to re-link. Show a clear call to action and re-initiate the connection.failed— bad credentials, or the aggregator reported a failure. Common after a password change at the bank.disconnected— the user revoked access on their side, or the link otherwise needs rebuilding.
Prefer not to build a bank picker at all? Use the hosted connect session —
POST /v3/clients/{id}/connect-session returns a LedgerSync-hosted url you email the user. Same lifecycle, none of the widget plumbing.Per-capability health
Anactive connection is not all-or-nothing. Each connection carries individual capabilities that can flip working or not-working on their own:
transactions, balance, available_balance, statements, check_images.
When one of these transitions, you get a connection.capability_changed webhook. To avoid flapping, LedgerSync applies hysteresis: 3 consecutive failed observations before a capability is marked failed, and 2 consecutive succeeded before it flips back. The event fires only on a real transition, not on every refresh, and includes previous_status, status, consecutive_observations, and observed_at.
This lets you show precise UI, for example “transactions are syncing, but statements are temporarily unavailable,” without dropping the whole connection.
Capability payload details
See the full
connection.capability_changed payload, statuses, and field-by-field breakdown in the Webhooks guide.Data freshness and refresh
Once a connection isactive, LedgerSync keeps its data fresh in the background. You do not poll for new data — you listen for refresh events:
account.refresh.completed— a refresh finished and new transactions/balances are available to read.account.refresh.failed— a refresh attempt failed. If failures persist, the connection may move tofailedor a capability may change.
account.refresh.completed as your signal to re-read /accounts and /transactions for that Client and connection.
Read after a refresh
Test the whole lifecycle in sandbox
You can drive this entire flow without a real bank. Search?q=FinBank, pick the row named exactly “FinBank”, and sign in with Banking Userid demo / Banking Password go. It flips to active immediately with no MFA and auto-populates accounts, transactions, and statements.
Connect a bank
The full initiate → widget → active walkthrough.
Webhooks
Every lifecycle event, its payload, and how to verify signatures.
Testing
Sandbox banks, MFA/OAuth variants, and credentials.
Errors
The error envelope and how to branch on
code.