connection.active webhook and the same data behind it. Choose based on how
much UI you want to own.
Hosted link
We host everything: bank search, login, MFA. You create a link and send it.
No
institution_id needed up front.Bring your own picker
You render the bank search from
GET /institutions, then start the
connection with the chosen institution_id.The hosted flow
You create a connect session for a client and get back a URL. Email it to your member. They open it, find their bank, log in on our page, and you get a webhook the moment it’s live.Bank credentials are entered only on the LedgerSync-hosted page. They
never touch your servers, and never come through the API as raw values.
Three calls, start to finish
1
Create the member as a Client
A Client is just your record of “this is Alice.”
Add
"phone" to store a contact number. To have LedgerSync text
the member a login invitation as soon as the Client is created, also
send "sms_invitation": true — a non-blank phone is required, or the
call returns 400.2
Create a connect session, get the link
Response
url to your member. That is the only link they need.3
Receive connection.active
Register a webhook once, and we tell you the instant the connection is live.Now read accounts, transactions, and statements for
Webhook
con_FINICITY_41294.Prefer to build your own picker?
If you already have onboarding UI, skip the hosted page. CallGET /institutions?q=chase to render the bank search inside your app, then
POST /clients/{id}/connections with the chosen institution_id. You still get
the same widget hand-off for credentials and the same connection.active
webhook, you just own the search box.
Redirect flow
When a member finishes on the aggregator widget (FINICITY / MX), the browser needs somewhere to land back in your app. Pass aredirect_url when you start
the connection and we bounce the member back to it with a short-lived
ls_token. Exchange that token server-side for the canonical connection id.
1
Register your callback URLs (once per environment)
Allowlist every URL you’ll redirect to. The match is exact — scheme,
host, port, and path must line up with what your app actually uses — and
URLs are https-only and per-environment (a sandbox URL does not
carry over to live). Each
POST overwrites the full set.Response
2
Pass redirect_url when you start the connection
Include
redirect_url in the initiate body. It must exactly match an
allowlisted entry or the call is rejected with 400 invalid_request.3
Member lands back on your callback with an ls_token
After the widget completes, the browser is redirected to your
redirect_url with an ls_token query parameter:4
Exchange the token for the connection id
Your callback POSTs the token back to verify it and recover the connection.
No API key is required on this call — the token’s signature is the trust
boundary.
Response
connection_id is the canonical con_SOURCE_id you use everywhere else
(accounts, transactions, statements).The redirect handshake is a browser convenience, not a substitute for
webhooks. If a member closes the tab before the redirect fires, you still get
connection.active — reconcile on the webhook, not on the callback landing.Embedding
However you launch the connect flow, the hand-off is the same: you open a LedgerSync-hosted URL, the member enters credentials only on our page, and you find out it worked. What differs is how you open that URL.Redirect or mobile webview (recommended)
The default, and the only mode you need for the hostedconnect-session flow:
open the URL as a full-page redirect (web) or in a system/webview (mobile).
Don’t try to frame it.
When you set a redirect_url, the member lands back on it after a successful
connect with a single-use ls_token appended:
POST /v3/connections/redirect-token/verify to read
the resulting connection_id.
Iframing the MX wrapper page
The MX Connect wrapper page (thewidget_url returned for MX connections) is the
one hosted page you may iframe. On success it notifies the parent window:
In sandbox the wrapper is served from
https://api-sandbox.ledgersyncappv2.com
— use that host for both the event.origin check and your frame-src.Iframe attributes and OAuth
The wrapper needs no Permissions-Policyallow attributes — it uses no
camera, microphone, or geolocation. A plain iframe works; widget_url is the URL
POST /clients/{id}/connections hands back:
sandbox attribute.
If your CSP forces one, it must permit scripts, forms, same-origin, and
popups — the widget runs JS, submits the credential form, needs its own origin,
and opens OAuth in a new tab:
postMessage is
for: after the origin check, listen for type === "ls/mx/memberConnected" and
use event.data.connection_id. You never need a redirect_url to know the
member finished.
What you cannot iframe
For the hosted
connect-session page, use a redirect or open it in a new tab —
framing it will be blocked by the browser.