Link and verify a bank account
Add an external bank account to your Account with a bank-linking flow or with routing and account numbers, and verify it with micro-deposits when needed.
An external bank account is a bank account at another bank. Once it's linked and verified, you can move money between it and your Wingspan Wallet, and use it as a funding source. See Payment and payout methods for where else it's used.
Prerequisites
- A credential with
payments.funding-source:write. - For a non-US account, the fields the country and currency need. See Bank account countries and institutions.
- If you call with a person's login session, a
BankAccountChangeMFA step-up. See Banking and spend.
Choose how to add the account
| Method | You send | Starts as |
|---|---|---|
| Bank-linking flow | publicToken and plaidAccountId | Verified, or Pending if the bank needs micro-deposits |
| Routing and account numbers | routingNumber, accountNumber, accountType, accountHolderType | Pending until you verify with micro-deposits |
The bank-linking flow is the safer choice. Account numbers never pass through your systems.
Link with the bank-linking flow
- Call
POST /v3/finance/external-bank-accounts/link. The body is optional. It takesredirectUriandinstitutionId. The response is201withlinkTokenandexpiresAt. - Open the bank-linking flow with
linkToken. When the account holder finishes, the flow gives you apublicTokenand theplaidAccountIdof the account they chose. - Call
POST /v3/finance/external-bank-accountswith both values. One request creates one bank account.
curl -X POST "https://api.wingspan.app/v3/finance/external-bank-accounts" \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: link-bank-2026-10-20-1" \
-d '{
"publicToken": "public-sandbox-1a2b3c",
"plaidAccountId": "plaid-account-4d5e6f",
"nickname": "Operating account"
}'accountHolderType (Individual or Business) is optional here. It overrides the holder type the bank reports. An older field name, providerPublicToken, still works in place of publicToken. Use publicToken.
Link with routing and account numbers
Send routingNumber, accountNumber, accountType (Checking or Savings), and accountHolderType. All four are required.
curl -X POST "https://api.wingspan.app/v3/finance/external-bank-accounts" \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: link-bank-2026-10-20-2" \
-d '{
"routingNumber": "021000021",
"accountNumber": "000123456789",
"accountType": "Checking",
"accountHolderType": "Business",
"accountHolderName": "Acme Studio LLC",
"nickname": "Operating account"
}'// 201 Created (trimmed)
{
"id": "Ba7Hn2Qx5Rt8Kw3Lp6Zb9D",
"status": "Pending",
"accountName": "Operating account",
"accountType": "Checking",
"accountHolderType": "Business",
"accountNumberMask": "6789",
"routingNumber": "021000021",
"currency": "USD",
"country": "US",
"verificationMethod": "MicroDeposit",
"microDepositStatus": "Requested",
"capabilities": ["Charge", "Payout"],
"subject": { "type": "Account", "id": "Ac2Wn8Qk5Lx7Bz3Mh4Rc9T" }
}Other fields you can send:
| Field | Notes |
|---|---|
accountHolderName | The name at the bank. If you leave it out, Wingspan uses your Account's verified legal name. |
country, currency | Default to US and USD. A non-US country registers an international account. |
routingSecondary | A second routing value some countries need, such as a Canadian transit number |
beneficiaryNameLocal, beneficiaryBankAccountType | Needed only for some corridors. See Bank account countries and institutions. |
subject | Who the account belongs to. It defaults to your Account. Use { "type": "Payee", "id": "..." } for a destination you manage for a payee. |
isDefault | Make this your default account. It must be false for a Payee or Payer subject. |
nickname | A label for the account |
Always send an Idempotency-Key. A retry with the same key and body returns the original result, with no second bank account and no second round of micro-deposits.
Verify with micro-deposits
When status is Pending and microDepositStatus is Requested or Initiated, Wingspan sends small deposits to the account. When they arrive, confirm the amounts with POST /v3/finance/external-bank-accounts/{bankAccountId}/verify.
curl -X POST "https://api.wingspan.app/v3/finance/external-bank-accounts/Ba7Hn2Qx5Rt8Kw3Lp6Zb9D/verify" \
-H "Authorization: Bearer $WINGSPAN_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "amounts": [0.12, 0.34] }'The response is the bank account. When the amounts match, status becomes Verified and events.verifiedAt is set.
If the account was created without starting micro-deposits, ask for them with PATCH /v3/finance/external-bank-accounts/{bankAccountId} and { "verificationMethod": "MicroDeposit" }. This works on a manually entered account that hasn't already started.
Statuses
status | Meaning |
|---|---|
Pending | Waiting for verification |
Verified | Ready to use |
Disconnected | The bank login broke. See Manage a bank account. |
Failed | The account could not be set up |
Removed | You removed it |
Errors
| Status | Meaning |
|---|---|
403 | Missing payments.funding-source:write, or StepUpMfaRequired |
404 | A Payee or Payer subject doesn't exist, or isn't yours |
409 | IdempotencyKeyConflict, or the request conflicts with the account's current state |
422 | A required field is missing or malformed, subject.type isn't Account, Payee, or Payer, or metadata was sent. |
422 | detailCode: users.ExternalBankAccountHolderNameUnavailable: no accountHolderName was sent and Wingspan couldn't derive one. Send accountHolderName. |
Webhook events
ExternalBankAccount.Created and ExternalBankAccount.Verified are published. ExternalBankAccount.Removed and ExternalBankAccount.Updated are also published. See Webhooks and events.
Related pages
Updated 12 minutes ago