Add Customer External Bank Accounts
How do I connect a customer's bank account using Plaid?
Overview
Connect customer bank accounts to the Cybrid Platform with Plaid Link. Cybrid generates a Plaid Link token; you drive the Plaid Link SDK in your application and submit the resulting public_token back to Cybrid to create the external bank account. This is the typical embedded experience and requires no Plaid account of your own.
For a high-level overview, see Plaid Integration.
Prerequisites
- An active Cybrid account and API credentials.
- A
customer_guidfor the user who will link their bank account. See Create a Customer. - On API version
2026-09-01or later, a customer who has completed KYC (individual) or KYB (business); otherwise the create returns400withunverified_customer. At banks that verify individuals non-documentarily, an individual customer whose attested personal details have passed may link the account before verification completes, because the account is what authenticates them. - A
redirect_uri(web) registered with Cybrid. - For Android: an
android_package_nameregistered with Cybrid support. Separate registrations are required for sandbox and production environments.
EnvironmentsExamples use the sandbox base URL
https://bank.sandbox.cybrid.app. For production, usehttps://bank.production.cybrid.app.
Link the bank account with Plaid Link
Cybrid generates the Plaid Link token. You initialize Plaid Link in your application and submit the resulting public_token back to Cybrid to create the external bank account.
Review the official Plaid Link documentation for up-to-date platform-specific details (Web, iOS, Android, or React Native).
Step 1: Generate a Plaid Link token
Create a workflow with type: plaid and kind: link_token_create. Cybrid asynchronously requests a plaid_link_token from Plaid. Poll the workflow endpoint until the token is available.
Workflow notes
- The
customer_guidis the customer who will link their bank account.- All redirect URIs must be registered with Cybrid.
POST /api/workflows
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"type": "plaid",
"kind": "link_token_create",
"language": "en",
"link_customization_name": "default",
"customer_guid": "customer_guid",
"redirect_uri": "https://yourapp.com/api/plaid-redirect-uri"
}{
"guid": "workflow_guid",
"bank_guid": "bank_guid",
"customer_guid": "customer_guid",
"type": "plaid",
"state": "storing",
"failure_code": null,
"created_at": "2026-01-01T12:00:00.000Z",
"updated_at": "2026-01-01T12:00:00.000Z"
}Poll GET /api/workflows/{workflow_guid} until state is completed and plaid_link_token is populated:
{
"guid": "workflow_guid",
"bank_guid": "bank_guid",
"customer_guid": "customer_guid",
"type": "plaid",
"state": "completed",
"failure_code": null,
"plaid_link_token": "plaid_link_token",
"created_at": "2026-01-01T12:00:00.000Z",
"updated_at": "2026-01-01T12:00:05.000Z"
}Workflow states
| State | Description |
|---|---|
storing | The Platform is storing the workflow details in the private store. |
completed | The Platform has created the workflow and generated the link token. |
failed | The workflow was not completed successfully (check failure_code for details). |
OAuth and redirect URIs
Many financial institutions use an OAuth flow, which redirects the customer to their bank's portal for authentication. Register your application's redirect URIs with Cybrid for each environment (sandbox and production). Contact Cybrid support to register your URIs.
If a customer starts Plaid Link from an in-app browser such as WhatsApp or Instagram, ask them to open your site directly in Safari or Chrome before beginning the bank connection. Some banks complete authentication in their own app or website, and in-app browsers may not return to the original Plaid Link session correctly.
For more information, see Plaid's documentation on OAuth redirect URIs.
If you need to support these flows more reliably, use Plaid's redirect flow: pass a redirect_uri when creating the link_token_create workflow, and have the redirect page restart Plaid Link with the URL it was opened with.
Sandbox redirect URIsSandbox only supports HTTPS redirect URIs. Separate registrations are required for sandbox and production environments.
Android apps
Use the android_package_name field when creating the Plaid workflow:
POST /api/workflows
Content-Type: application/json
Authorization: Bearer YOUR_TOKEN
{
"type": "plaid",
"kind": "link_token_create",
"language": "en",
"link_customization_name": "default",
"customer_guid": "customer_guid",
"android_package_name": "com.yourcompany.yourapp"
}For Android applications, register your app's package name (e.g., com.yourcompany.yourapp) with Cybrid support instead of a redirect URI.
Step 2: Initialize Plaid Link SDK
Use the generated plaid_link_token to initialize the Plaid Link SDK in your client application. The handler below includes callbacks for success, exit, and other events.
const handler = Plaid.create({
token: 'GENERATED_LINK_TOKEN', // The plaid_link_token from Step 1
onSuccess: (public_token, metadata) => {
// A Plaid public_token and account metadata are returned
// Use these values to create an external bank account in Step 3
// https://plaid.com/docs/link/web/#onsuccess
},
onLoad: () => {},
onExit: (err, metadata) => {
// Handle the case where the user exits the flow
// https://plaid.com/docs/link/web/#onexit
},
onEvent: (eventName, metadata) => {
// Optionally subscribe to events during the Plaid flow
// https://plaid.com/docs/link/web/#onevent
},
});
Callback loggingImplement clear logging for each callback in your application.
Sandbox environmentIn the Cybrid Sandbox, the generated
plaid_link_tokenautomatically initializes the Plaid Link SDK in its sandbox mode, so you can test without using real personal information.
Sandbox ownership outcomeFrom API version
2026-09-01, the holder-name match runs while the account is created, so you do not need to match a Persona name to Plaid's sandbox identity. In sandbox, includeexpected_behaviours(passed_immediatelyorfailed_immediately) in the create request in Step 3 to choose the customer holder-name-match outcome. See Verify External Bank Accounts.Legacy, before
2026-09-01: match the customer's Persona-verified name to Plaid's default sandbox identity (Alberta Charleson) before linking the Plaid account. Without a name match, the downstream bank account identity verification does not auto-pass and fails into Persona document submission. See Verify an Individual Customer for the exact values to enter.
Handling callbacks
onSuccess
onSuccessFires when the customer successfully links an account. Returns a public_token and a metadata object containing the account id. You use both values in the next step.
One account per workflowA customer can only link one external bank account per workflow. To link multiple accounts, create a unique workflow for each.
onExit
onExitFires when the customer leaves the flow without linking an account, with optional err and metadata arguments. To diagnose drop-off causes, log only scrubbed or redacted details or non-sensitive fields such as request IDs, metadata.status, and high-level error codes or messages. Do not persist raw metadata or account/institution details in application logs.
Empty exit metadataWhen a returning customer exits during Plaid's SMS verification step — before selecting an institution or entering credentials —
metadata.institutionisnullandmetadata.accountsis[].metadata.statusmay also return""instead of a step name likechoose_device. This is a known Plaid edge case where exit metadata cannot be populated from cached state.
Share error details with CybridWhen troubleshooting SDK issues, capture and share callback error details —
err, request IDs,metadata.status, and high-level error codes — with Cybrid support. These details help diagnose integration issues faster.
Step 3: Create the external bank account
With the public_token and account id (inside the accounts array of metadata) from the onSuccess callback, call POST /api/external_bank_accounts with the Plaid-specific parameters.
POST /api/external_bank_accounts
Content-Type: application/json
Accept-Version: 2026-09-01
Authorization: Bearer YOUR_TOKEN
{
"name": "Customer Checking Account",
"account_kind": "plaid",
"customer_guid": "customer_guid",
"asset": "USD",
"plaid_public_token": "plaid_public_token",
"plaid_account_id": "plaid_account_id"
}{
"guid": "external_bank_account_guid",
"name": "Customer Checking Account",
"account_kind": "plaid",
"customer_guid": "customer_guid",
"asset": "USD",
"state": "storing",
"created_at": "2026-01-01T16:05:40.757Z"
}The create returns the account in storing. On API version 2026-09-01 or later, the Platform then verifies ownership as part of account creation:
- A customer-owned account moves to
pending. If the holder-name match is inconclusive, it stayspendingwhile the customer completes a Persona inquiry: find the Platform'stype: bank_account,method: account_ownershipidentity verification withGET /api/identity_verifications?external_bank_account_guid={external_bank_account_guid}, and once it iswaitingwith apersona_inquiry_id, open the inquiry withPOST /api/persona_sessions. When the customer completes it, the account moves toreviewing. It endscompleted,failedwithownership_mismatch, orexpired. - A counterparty account's ownership is implied to be the counterparty's. It usually moves
storing→pending→completed. If the counterparty matches an existing identity that needs a compliance review, it moves toreviewingand endscompleted,failedwithcompliance_rejection, orexpired. - A bank-level account is attested by the bank. It moves
storing→pending→completed; if the provider rejects the bank's legal-entity details, it waits inreviewingwhile Cybrid repairs them.
Poll GET /api/external_bank_accounts/{external_bank_account_guid} with Accept-Version: 2026-09-01; on earlier versions pending and reviewing read as storing and expired as failed. Stop polling on failed or expired.
You do not create a type: bank_account identity verification yourself; creating one for an account created on API version 2026-09-01 or later returns 422 with deprecated. In sandbox, send expected_behaviours with passed_immediately or failed_immediately in the create request to simulate the customer holder-name match. See Verify External Bank Accounts.
Before API version 2026-09-01Without the
Accept-Version: 2026-09-01header on an earlier version, ownership is verified downstream by atype: bank_accountidentity verification after the account is created, andpending,reviewingandexpiredare not reported. See API Versioning.
For a detailed walkthrough, see the recipe Adding a Customer's Verified External Bank Account.
Withdrawal hold on new accountsA newly created external bank account cannot receive withdrawals until its
usable_attime. Withdrawals created earlier return422withmessage_code: destination_too_new. Deposits are not affected. See New Withdrawal Destination Holds.
Payment rail routing
ACH vs. wire routingWhen a bank account is linked via Plaid, the ABA routing number is captured and stored. When initiating a transfer, specify the
payment_railparameter (achorwire) to process the payment using the appropriate routing. Some US banks have different routing numbers for ACH versus wire transactions. If wire transfers fail due to routing issues with a Plaid-linked account, use the counterparty external bank accounts method to explicitly specify routing details for the intended payment rail.Wires also require the account holder's address, which ACH does not. Plaid doesn't always return it. If a wire to a Plaid-linked account is rejected for a missing address or wire routing details, use the counterparty external bank account method, or contact support to add the address.
Refresh expired connections
Plaid bank connections expire periodically and require the customer to re-authenticate. When this happens, the external bank account's state transitions to refresh_required. To restore the connection, follow the steps in Refresh External Bank Accounts.
Next steps
Updated 9 days ago

