Sandbox uses
https://api-sandbox.ledgersyncappv2.com/v3 and an sk_test_...
key. Live traffic never touches sandbox and vice-versa. See
Authentication for how to get and send your key.What sandbox gives you
- The real routing engine. When you initiate a connection, v3 picks the source (Finicity, MX, or FDE) from the institution catalog, server-side, just like production. You never pass a
source. - Real widgets. Finicity and MX open their own aggregator sandbox widgets. FDE opens the LedgerSync-hosted connect page. You still never see or transmit credentials.
- Real webhooks. Every state transition publishes a real Kafka event and fires the matching webhook, HMAC-signed exactly like production.
Sandbox institution IDs
Pass one of these asinstitution_id on POST /v3/clients/{id}/connections. v3
resolves the source from the catalog row, you don’t pass a source field.
FinBank (Finicity)
ins_3c97c5da90335a04Finicity’s sandbox bank. Widget renders on connect2.finicity.com (Finicity institutionId 8906).MX Bank (MX)
ins_73df5077e90a1b63MX’s sandbox bank (mxbank). Hands off to MX Connect’s sandbox on int-widgets.moneydesktop.com. Unlimited test aggregations.Ledgersync Bank (FDE)
ins_a7397a8d0656e1b7FDE’s credential-based extractor. Returns a LedgerSync-hosted connect page (not an aggregator widget); accepts any login, offers one account. The only sandbox bank that returns check images.The 60-second smoke test (FinBank)
FinBank is Finicity’s sandbox bank. It flips toactive immediately, with no
MFA, and auto-populates accounts, transactions, and statements. It is the
fastest way to prove your integration works end-to-end.
1
Find FinBank
Search the catalog and pick the row named exactly Grab the
FinBank — the sandbox has several FinBank * variants (see the table below) and only the plain one is the no-friction path.Search
id (ins_3c97c5da90335a04) from the row whose name is FinBank.2
Initiate the connection
Point it at a client you’ve created (see Connect a bank for creating clients).You get
Initiate
202 Accepted with an operation_id. Poll it until status is succeeded, then read result.connection.action.widget_url.3
Open the widget and sign in
Open
widget_url in a browser, pick FinBank, and sign in with the sandbox credentials:No MFA, it links right away. (
customer1 / go also works and returns known balances, see the table below.)4
Watch it go active
The connection flips to
active and connection.active fires on your webhook. The payload carries the canonical connection id (con_FINICITY_<bankAccountId>, e.g. con_FINICITY_41294). Store that, the placeholder con_<uuid> you saw during pending only works for the widget step.5
Read the data
Accounts, transactions, and statements are already populated. Reads are Client-scoped, so pass Transaction ids follow the same shape:
client_id every time.Accounts
txn_FINICITY_..., txn_MX_..., or txn_FDE_....If you saw
connection.active, a canonical con_FINICITY_... id, and non-empty accounts, your happy path is wired correctly. Now exercise the edge cases.Finicity — FinBank credentials
Public sandbox logins published by Mastercard Open Banking. Sign in on the widget URL forins_3c97c5da90335a04. The username picks the scenario;
the password is go unless noted.
FinBank Profiles A–H — account-shape coverage
For theFinBank Profiles - A / - B institutions, the password picks a
profile (use the same value as the OAuth username too). Exercises every
serializer path; investment coverage is intentionally thin, link a real
brokerage for that.
Other FinBank variants — edge-case banks
The catalog also surfaces several letter- and label-suffixed FinBank entries. Each one is Mastercard Open Banking’s dedicated sandbox bank for a specific edge case, pick a variant when you want to harden against that case, not for a smoke test. Search for each by name and initiate exactly as above.MX sandbox bank (mxbank)
MX is the alternate aggregator, it catches institutions Finicity misses, and v3 routes to it automatically when the catalog says so. Sign in on the widget URL forins_73df5077e90a1b63. The username is always mxuser; the password is
the dial that picks the scenario. Ids come back source-tagged: con_MX_<id>
(e.g. con_MX_1224), acc_MX_..., txn_MX_....
What v3 reports for each MX
connection_status above: DENIED reads requires_action with last_error.code credentials_invalid; LOCKED reads requires_action with account_locked_by_institution and is_user_actionable: false (the user acts at the bank, not in the widget); FAILED maps to failed with institution_unavailable, but neither SERVER_ERROR nor UNAVAILABLE creates a connection in the current sandbox, so that case cannot be seen here. These are the statuses GET /v3/connections/{id} reports. A first link that ends in one of them is announced by a connection.* webhook carrying the canonical con_MX_... id, shortly after the user submits the widget (6 to 20 seconds in September 2026 tests). Before September 23, 2026 only the initiate event under the placeholder id fired, so a first link that did not pass through MFA was not named by a webhook until its status next changed.Card terms and masks in sandbox
The sandbox banks publish very little credit data, so an emptyliabilities on a sandbox card is expected, not a bug:
- FinBank credit cards carry no card terms. Use the FinBank line of credit to see a populated object: it carries
available_credit,statement_balance,minimum_paymentandpayment_due_date(nocredit_limit). The FinBank auto loan and mortgage carry aminimum_paymentonly. - The MX Bank credit card carries
available_credit(3000) and a fixedpayment_due_dateof 2021-05-07; it reports nocredit_limitorminimum_payment. The MX Bank loan and mortgage carry a fixed 2021-05-12 due date. A due date in the past is the bank’s last published cycle, not an overdue payment. - Checking, savings and investment accounts never carry
liabilities, even though MX Bank reports a due date on them.
mask is the last 4 digits of the account number on every source: FinBank reports 7777 for the credit card, 6666 for the line of credit, 1111 for checking and 2222 for savings; MX Bank accounts report the last 4 of their masked account numbers.
FDE — Ledgersync Bank
FDE is LedgerSync’s proprietary, credential-based extraction path, used for banks neither aggregator covers. It connects through a LedgerSync-hosted page (not an aggregator widget). The sandbox Ledgersync Bank (ins_a7397a8d0656e1b7) accepts any username and password:
Initiate an FDE connection
widget_url and:
1
Enter any credentials
Type any username / password → Submit.
2
Select the account
An account-selection form appears (one account is offered) → Submit to keep it.
3
Watch it finish
FDE downloads the statements and check images, then fires
connection.active. The connection id is con_FDE_... and transactions come back as txn_FDE_....institution_id, there is no bank-search
step, and you never handle the credentials yourself.
Check images
Ledgersync Bank is the only sandbox bank that produces check images — the Finicity and MX sandbox banks have none, so it’s the one place to exercise the check routes end-to-end. Onceconnection.active lands, read them off the
account:
List the checks FDE extracted
Extraction is asynchronous — checks appear once the connection reaches
active, not at the moment the widget closes. Wait for the webhook rather than
polling straight after submitting credentials.read:checks scope, and why bank-reported and OCR fields are kept apart.
Which webhook fires when
Every state transition publishes a real Kafka event and fires the matching webhook, HMAC-signed exactly like production.
These are the connection-state events you’ll drive in sandbox. The full catalog
(
connection.capability_changed, account.refresh.completed,
account.refresh.failed) and every payload shape is in Webhooks.
Testing your webhook handler
You don’t have to link a bank to prove your endpoint works. Fire a test event:Test-fire a subscription
event_types filter, so it reaches your handler even for events you didn’t
subscribe to. It arrives as a webhook.test event, a safe way to confirm
signature verification, your 2xx-within-30s response, and your event_id dedupe
logic before real events flow.
Common-error recipes
Deliberately trigger each failure mode to harden your handler:- Invalid credentials — initiate FinBank, sign in
customer1/bad. The widget rejects the sign-in and keeps the user on the form. No connection is created and no webhook fires, so there is nothing to handle until the user signs in successfully. (FinBank Decertified, once used for a decertificationlast_error, links normally as of September 2026.) - MFA wrong answer — initiate Finicity, sign in
customer3/go, submit any OTP other than999999. As of September 2026 no MFA challenge appears for this login, so this path cannot be reproduced. - MX locked user — initiate MX, sign in
mxuser/LOCKED.connection_status = LOCKED; the connection is created and readsrequires_actionwithlast_error.code = account_locked_by_institutionandis_user_actionable: false. A secondconnection.requires_actionfollows within seconds, this one under the canonicalcon_MX_...id and carrying thatlast_error. Store that id. - Upstream 503 — initiate MX, sign in
mxuser/UNAVAILABLE. Meant to simulate an institution-side 503 (institution_unavailable, retryable), but as of September 2026 the MX widget stays on “Loading” and no connection is created. requires_actionthat lingers — start a connection and don’t finish the widget. It staysrequires_action(no auto timeout-to-failed today) until the user re-opens the still-validwidget_urlor you re-initiate.- Refresh round-trip — link
customer1/go,POST /v3/connections/{id}/refresh, re-readGET /v3/accounts/{id}/transactionsfor the diff. - Disconnect —
DELETE /v3/connections/{id}.connection.disconnectedfires; further reads return 404. - Error envelopes — call a read with a bogus
client_idor a missing key to see the{ "error": { "code", "message", "type", "doc_url", "trace_id" } }shape. Branch oncodeand quoteX-LS-Trace-Id. See Errors.
Sandbox keys can’t hit live data and live keys can’t hit sandbox banks — see
insufficient_scope if you mix them up. Finicity test
customers are auto-pruned after ~30 days of inactivity; MX has no per-credential
cooldown.Connect a bank
The full initiate → widget → active flow, end to end.
Connection lifecycle
Every status a connection moves through and what each means.
Webhooks
Subscribe, verify signatures, dedupe, and handle retries.
Errors
The error envelope, the full code catalog, and how to branch on
code.