Quick Start: Bank Linking
Introduction
Bank linking unifies a cardholder's card and bank activity into one transaction stream. Once a cardholder connects their bank, the transactionmessage.created, transaction.created, and transaction.updated webhooks you already operate start carrying bank-side activity — fees, transfers, payroll, loan payments, foreign-exchange markup — in the same schema, on the same handlers. Card transactions on linked cards also settle earlier: hours after the swipe rather than days.
This page maps the whole journey end to end and links out to the detail. Each step is a real API call; the cardholder's only interaction is Plaid's hosted bank-connect flow.
The two-step modelBank linking is bank-first. The cardholder connects their bank, and that alone unlocks bank-only events (fees, transfers, payroll…). Linking cards is a second, optional step that adds earlier settlement for card spend — do it now, later, or never. The two are independent; see Linking Cards to Bank Accounts.
What you get
| Requires | What it delivers | |
|---|---|---|
| Bank-only events | Bank connected | Net-new SETTLED transactions for fees, transfers in/out, loan payments, income, FX markup — activity with no card-side counterpart. |
| Earlier settlement | Bank connected and card linked | Card authorizations flip PENDING→SETTLED on bank evidence, typically within hours of the swipe. |
Both ride the platform's existing webhooks — no new event types, no schema changes. Full payload detail is in Bank Linking Webhooks & Events.
The flow at a glance
sequenceDiagram
autonumber
participant You as Your app
participant CH as Cardholder
participant Astrada as Astrada API
participant Plaid
You->>Astrada: POST /bank-links (id + subaccountId)
Astrada-->>You: hostedLink (Plaid, 4-hour TTL)
You->>CH: Distribute the hostedLink
CH->>Plaid: Choose bank, sign in, select accounts
Plaid-->>Astrada: Session finished
Astrada-->>You: banklink.completed (bankAccounts[])
Note over You,Astrada: Bank-only events begin immediately
You->>Astrada: (optional) PATCH /bank-accounts/{id} — link cards
Note over You,Astrada: Linked cards now settle earlier
Steps
1. Authenticate
Get an access token for the Astrada API (all calls below go to the production gateway, https://api.astrada.co). Bank linking uses three scopes:
| Scope | Covers |
|---|---|
banking:read | Everything readable — list links, list bank accounts, check state. |
banking:write | The per-cardholder write surface — create links, link/unlink cards, re-authenticate a connection. |
banking:admin | Operational and destructive actions — on-demand sync, disconnect. |
See Authentication.
2. Create a bank link
POST /bank-links with your correlation id and the cardholder's subaccountId. The response carries a Plaid hostedLink you distribute to the cardholder; it has a 4-hour TTL.
POST /bank-links HTTP/1.1
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"id": "enroll-user-456",
"subaccountId": "81e7c267-0927-4234-a2bd-78cef799de54"
}curl -X POST https://api.astrada.co/bank-links \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"id": "enroll-user-456",
"subaccountId": "81e7c267-0927-4234-a2bd-78cef799de54"
}'{
"id": "enroll-user-456",
"subaccountId": "81e7c267-0927-4234-a2bd-78cef799de54"
}| Field | Type | Required | Description |
|---|---|---|---|
id | string (alphanumeric + _/-, ≤256) | Yes | Your correlation key. Map it back to your user/card row. |
subaccountId | UUID | Yes | The subaccount that routes events and scopes card linking. |
Response (201 Created):
{
"id": "enroll-user-456",
"accountId": "d8fae7c0-0f5b-4d6b-8cce-71764e3f515f",
"subaccountId": "81e7c267-0927-4234-a2bd-78cef799de54",
"state": "pending",
"hostedLink": "https://secure.plaid.com/link/lp9o97618r1r6p93oon62nr025950ro4s3",
"linkExpiresAt": "2026-04-27T16:00:00Z",
"createdAt": "2026-04-27T12:00:00Z"
}3. The cardholder connects their bank
Distribute the hostedLink. The cardholder completes Plaid's hosted flow — there's no UI for you to build, and Astrada never sees their banking credentials. The four screens below are what the cardholder walks through.
a. Choose a bank
The cardholder searches for and selects their institution.

b. Sign in
Plaid collects the bank credentials and authenticates directly with the institution.

c. Select accounts
The cardholder chooses which accounts to share (checking, savings, …).

d. Done
Plaid confirms the connection and returns the cardholder to your flow.

Credentials never touch AstradaThe cardholder authenticates inside Plaid's UI. Plaid hands Astrada an opaque access token — the bank username and password never enter Astrada's systems. See Privacy below.
4. Detect completion
There are two robust ways to know the cardholder finished, and you can use either:
Webhook — banklink.completed:
{
"bankLinkId": "enroll-user-456",
"bankAccounts": [
{ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Chase Checking" },
{ "id": "f9e8d7c6-b5a4-3210-fedc-ba0987654321", "name": "Chase Savings" }
]
}Polling — GET /bank-links/{id} (scope banking:read): this endpoint doubles as a completion detector. If the cardholder finished but the webhook hasn't arrived, calling GET transitions the link to completed and returns the discovered accounts inline. It also transparently mints a fresh hostedLink if you GET a pending link whose linkExpiresAt has passed — you never handle expiration by hand.
{
"id": "enroll-user-456",
"state": "completed",
"hostedLink": null,
"completedAt": "2026-04-27T14:30:00Z",
"bankAccounts": [
{ "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Chase Checking" },
{ "id": "f9e8d7c6-b5a4-3210-fedc-ba0987654321", "name": "Chase Savings" }
]
}At this point bank-only events already fire on the connected accounts. Read the full account records — including the cardIds currently linked to each — with GET /bank-accounts?subaccountId={subaccountId}.
5. Link cards for earlier settlement (optional)
Linking a card to a bank account is what turns on earlier settlement for that card. In most integrations you don't have to do anything: card links are detected automatically the first time a card's transactions produce a high-confidence match against a bank account's feed.
To pre-link a card before any spend exists — or to correct an assignment — call PATCH /bank-accounts/{id} with the desired cardIds:
PATCH /bank-accounts/a1b2c3d4-e5f6-7890-abcd-ef1234567890 HTTP/1.1
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"cardIds": ["5dec2c49-0aa5-4683-a317-427eb5d115f3"]
}curl -X PATCH https://api.astrada.co/bank-accounts/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"cardIds": ["5dec2c49-0aa5-4683-a317-427eb5d115f3"]
}'{
"cardIds": ["5dec2c49-0aa5-4683-a317-427eb5d115f3"]
}Multi-card linking, backfill, and the linking constraints (a card links to one account at a time, etc.) are covered in Linking Cards to Bank Accounts.
Bank-only modeYou can skip card linking entirely and use bank linking as a standalone bank-data source. Every bank transaction still surfaces (as
banktransaction.createdand a SETTLEDtransaction.created), but with no card you won't receive card authorizations ortransaction.match.created. See Bank-only mode.
6. Receive events
From here, bank activity flows on the webhooks you already handle. A payroll deposit arrives as a transaction.created; a linked card's swipe settles early as a transaction.updated. New to Astrada webhooks? Register an endpoint with POST /webhooks, listing the event types you want to receive. See Bank Linking Webhooks & Events for every event and its payload.
Keep connections healthy
Plaid connections occasionally need re-authentication — a changed bank password, an expiring consent, or an MFA refresh. When that happens the bank account's state flips to auth_required, a bankaccount.state_changed webhook fires with the reason, and bank-driven events stop for that account until the cardholder re-connects.
- Detect it: subscribe to
bankaccount.state_changed, or pollGET /bank-accounts?state=auth_required(hourly is plenty). - Fix it:
POST /bank-accounts/{id}/refresh(scopebanking:write— re-auth is customer self-serve) returns a fresh Plaid update-mode hosted link. The cardholder completes the same Plaid flow to re-authenticate; the bank account ID stays the same and events resume on the next sync. - When it can't be fixed: if the connection is revoked or the item is permanently lost, the account goes to
disconnectedinstead — refresh won't recover it, so you disconnect it and re-enroll with a new bank link.
Disconnect a connection
When a cardholder opts out — or you need to clear a disconnected connection before re-enrolling — tear it down. Both calls take scope banking:admin.
- Remove the whole connection:
DELETE /bank-links/{id}revokes the item with Plaid and deletes its bank accounts and their transactions. Returns204 No Content. - Remove a single account:
DELETE /bank-accounts/{id}drops one bank account and its transactions while leaving the rest of the connection intact. Returns204 No Content.
On-demand syncBank data syncs on its own — once when the link completes, then on Plaid's update webhooks — so you rarely pull by hand. When you need data now (e.g. reconciling right after a known deposit),
POST /bank-links/{id}/sync(scopebanking:admin) forces a sync immediately.
Privacy
Three guarantees, each with a concrete control:
- Astrada never stores Plaid credentials. The cardholder authenticates inside Plaid's hosted UI; Astrada holds only the opaque access token Plaid returns.
- Account numbers are stored only as a short mask. Every bank account exposes
mask(typically the last 4 digits). The full account number and routing number are not stored, logged, or returned in any payload. - Subaccount scoping is enforced at the routing layer. Every event fans out only to subscribers scoped to the originating
subaccountId— no cross-subaccount leakage.
Next steps
- Linking Cards to Bank Accounts — auto-detection, manual linking, and multi-card rules.
- Bank Linking Webhooks & Events — every event and its payload.
- Webhook Event Types — the canonical event catalog.