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

Choose how to add the account

MethodYou sendStarts as
Bank-linking flowpublicToken and plaidAccountIdVerified, or Pending if the bank needs micro-deposits
Routing and account numbersroutingNumber, accountNumber, accountType, accountHolderTypePending 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

  1. Call POST /v3/finance/external-bank-accounts/link. The body is optional. It takes redirectUri and institutionId. The response is 201 with linkToken and expiresAt.
  2. Open the bank-linking flow with linkToken. When the account holder finishes, the flow gives you a publicToken and the plaidAccountId of the account they chose.
  3. Call POST /v3/finance/external-bank-accounts with 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:

FieldNotes
accountHolderNameThe name at the bank. If you leave it out, Wingspan uses your Account's verified legal name.
country, currencyDefault to US and USD. A non-US country registers an international account.
routingSecondaryA second routing value some countries need, such as a Canadian transit number
beneficiaryNameLocal, beneficiaryBankAccountTypeNeeded only for some corridors. See Bank account countries and institutions.
subjectWho the account belongs to. It defaults to your Account. Use { "type": "Payee", "id": "..." } for a destination you manage for a payee.
isDefaultMake this your default account. It must be false for a Payee or Payer subject.
nicknameA 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

statusMeaning
PendingWaiting for verification
VerifiedReady to use
DisconnectedThe bank login broke. See Manage a bank account.
FailedThe account could not be set up
RemovedYou removed it

Errors

StatusMeaning
403Missing payments.funding-source:write, or StepUpMfaRequired
404A Payee or Payer subject doesn't exist, or isn't yours
409IdempotencyKeyConflict, or the request conflicts with the account's current state
422A required field is missing or malformed, subject.type isn't Account, Payee, or Payer, or metadata was sent.
422detailCode: 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


Did this page help you?