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_0a01a5430925d0b2Finicity’s sandbox bank. Widget renders on connect2.finicity.com (Finicity institutionId 8906).MX Bank (MX)
ins_eaee71edf17fdacdMX’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 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.
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_0a01a5430925d0b2) from the row whose name is FinBank.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.Open the widget and sign in
Open
No MFA, it links right away. (
widget_url in a browser, pick FinBank, and sign in with the sandbox credentials:| Field | Value |
|---|---|
| Banking Userid | demo |
| Banking Password | go |
customer1 / go also works and returns known balances, see the table below.)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.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_0a01a5430925d0b2. The username picks the scenario;
the password is go unless noted.
| Scenario | Username | Password | MFA | Expected result |
|---|---|---|---|---|
| Happy path (verified) | customer1 | go | none | Checking 1111 ≈ 605.06 / 8 txns (Walmart fixtures). |
| Happy path (legacy demo) | demo | go | none | Checking + Savings, small synthetic history. |
| Wider shape | customer2 | go | none | Checking + Savings + Credit Card — multi-type serialization. |
| Text MFA | tfa_text | go | text prompt · answer go | Drives connection.requires_action → connection.active. |
| OTP text MFA | customer3 | go | OTP challenge · answer 999999 | Submit a wrong code to test the failure path. |
| Image MFA | tfa_image | go | image challenge · answer go | Exercises the image-MFA UI path. |
| Captcha | tfa_captcha | go | captcha · answer any non-empty | Any non-empty value passes. |
| Invalid credentials | customer1 | bad | n/a | connection.failed, last_error = invalid_credentials. |
| Locked institution | locked | go | n/a | connection.failed, last_error = institution_locked. |
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.
| Password (and OAuth username) | Account shapes returned |
|---|---|
profile_02 | Savings, IRA, 401k, Credit Card |
profile_03 | Checking, Savings, Credit Card |
profile_04 | Checking + Money Market + Mortgage |
profile_05 | Checking, Savings, Credit Card, Auto Loan |
profile_06 | Checking, Savings, Brokerage, Credit Card |
profile_07 | Checking + Student Loan + Line of Credit |
profile_08 | Multiple Checking accounts |
profile_09 | Multiple Credit Cards |
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.| Catalog name | Purpose |
|---|---|
| FinBank Profiles - A | Account-shape coverage. The password picks a profile (profile_02…profile_09) that returns a specific mix of Checking, Savings, IRA, 401k, Credit Card, Brokerage, Mortgage, Auto Loan, etc. |
| FinBank Profiles - B | Second profile-driven institution — same profile_* password mechanism as Profiles - A, distinct catalog row. |
| FinBank M | MFA-flavoured variant (the “M” suffix). Use alongside the text / image / OTP MFA usernames to exercise the connection.requires_action → resume path. |
| FinBank O | Alternate-aggregation variant. Use when you need a non-default Finicity routing for parsing or fixture-level regression coverage. |
| FinBank C | Captcha-flavoured variant. Pair with the tfa_captcha username to drive the captcha-MFA path in the widget. |
| FinBank P | Personal-banking variant. Use it to keep a consumer-shaped connection isolated from your business test connections. |
| FinBank OAuth | OAuth sign-in flow. The widget redirects to a Finicity-hosted bank sign-in instead of taking credentials inline. The catalog row has oauth_supported = true. |
| FinBank Decertified | Decertified-institution failure path. Drives connection.failed with a decertification-style last_error so you can verify your re-link CTA renders. |
| FinBank Billable | Billable-statements variant. Verify your code handles the statements path end-to-end, including whatever your billing/usage accounting needs to count. |
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_eaee71edf17fdacd. 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_....
| Scenario | Username | Password | Result |
|---|---|---|---|
| Happy path (verified) | mxuser | correct | Checking $495,565.18 / 49 txns, Savings, Credit Card, Loan, Mortgage, Investment. |
| Any non-keyword password | mxuser | <anything else> | Connects normally. Use any string other than the keywords below. |
| Text MFA | mxuser | challenge | Standard text MFA · answer correct (any other answer fails). |
| Pick-one MFA | mxuser | options | OPTIONS selection challenge · answer correct. |
| Image MFA | mxuser | image | IMAGE selection challenge · answer correct. |
| Bad request | mxuser | BAD_REQUEST | 400 missing fields. connection_status = FAILED. |
| Invalid credentials | mxuser | UNAUTHORIZED | 401 invalid credentials. connection_status = DENIED. |
| Invalid login | mxuser | INVALID | 401 login/password invalid. connection_status = DENIED. |
| Locked user | mxuser | LOCKED | 401 user is locked. connection_status = LOCKED. |
| Disabled user | mxuser | DISABLED | 401 locked-account variant. connection_status = DENIED. |
| Server error | mxuser | SERVER_ERROR | 500 institution-side error. connection_status = FAILED. |
| Service unavailable | mxuser | UNAVAILABLE | 503 service unavailable. connection_status = FAILED. |
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:
Because the bank is already fixed by
institution_id, there is no bank-search
step, and you never handle the credentials yourself.
Which webhook fires when
Every state transition publishes a real Kafka event and fires the matching webhook, HMAC-signed exactly like production.| Event | When it fires | How to trigger it |
|---|---|---|
connection.requires_action | As soon as the widget URL is ready (right after connection.initiated). | Payload carries data.action.widget_url — open it for the end-user. |
connection.active | User finishes the widget with valid credentials. | Finicity customer1 / go or MX mxuser / correct. Accounts now visible via GET /v3/accounts. |
connection.failed | Credentials rejected, MFA failed, or upstream pipeline error. | Finicity customer1 / bad or MX mxuser / UNAUTHORIZED. last_error carries a stable code. |
connection.disconnected | End-user revokes access or LedgerSync tears down the link. | DELETE /v3/connections/{id}, or Finicity locked / go. |
connection.initiated, 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 →
connection.failed— initiate FinBank, sign incustomer1/bad. Watchlast_error = invalid_credentialsland. (Or link FinBank Decertified for a decertificationlast_error.) - MFA wrong answer →
connection.failed— initiate Finicity, sign incustomer3/go, submit any OTP other than999999. - MX locked user — initiate MX, sign in
mxuser/LOCKED.connection_status = LOCKED,connection.failedfires. - Upstream 503 →
connection.failed— initiate MX, sign inmxuser/UNAVAILABLE. Simulates an institution-side 503;last_errorindicates retryable. 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.