The Plaid connector links Formance to Plaid's bank-aggregation platform. It drives the Open Banking PSU flow: create a Payment Service User, forward them to Plaid, hand them a Plaid Link session, and sync the resulting accounts, balances, and transactions back through Connectivity. Available from Payments 3.2.0.
Prerequisites#
You need a Plaid account and a clientID + clientSecret pair. The key needs access to Auth, Transactions, and Identity at minimum (set products on the Plaid dashboard before issuing the key).
Plaid signs every webhook with a JWT verified against Plaid's public key — no shared secret to configure.
Make sure to create an API key dedicated to Formance. Doing so will improve your auditability and security and will allow you to revoke access to Formance at any time if needed.
Installation#
curl -X POST $FORMANCE_API_URL/api/payments/v3/connectors/install/plaid \
-H "Content-Type: application/json" \
-d @config.jsonConfiguration fields#
| Field | Type | Required | Default |
|---|---|---|---|
clientID | string | Yes | |
clientSecret | string | Yes | |
isSandbox | boolean | No | |
pageSize | integer | No | 25 |
pollingPeriod | string | No | 30m |
isSandbox: true flips to Plaid's sandbox host (https://sandbox.plaid.com); production uses https://production.plaid.com. No separate "development" environment.
Capabilities#
FETCH_ACCOUNTS— depository, credit, and loan accounts per PSU connection.FETCH_BALANCES— available + current balance via Plaid's/accounts/balance/get.FETCH_EXTERNAL_ACCOUNTS— counterparty accounts identified by Plaid'sTransferproduct when present.FETCH_PAYMENTS— transactions via Plaid's incremental/transactions/synccursor.CREATE_WEBHOOKS+TRANSLATE_WEBHOOKS— webhooks per Item (Plaid's connection primitive), provisioned on link.
The connector doesn't initiate transfers or payouts — Plaid's Transfer product is read-only here. Use a PSP connector (Stripe, Increase) for outbound flows.
Account model#
Every Connectivity internal account is one Plaid Account on a linked Item (depository, credit, or loan). The reference is the Plaid account_id; name is the Plaid account name; defaultAsset is the account's ISO currency at standard precision. Each account is PSU-scoped — psuID carries the Connectivity PSU and openBankingConnectionID carries the Plaid Item. EXTERNAL accounts come from Plaid's Transfer product when present. See Accounts for the cross-connector model.
Linking a user#
Plaid's auth ceremony runs through Plaid Link. The Connectivity surface:
- Create a PSU —
v3CreatePaymentServiceUserreturns a PSU ID. - Forward to Plaid —
v3ForwardPaymentServiceUserToProvidercalls/link/token/createand stores thelink_tokenas PSU metadata. - Create a Link session —
v3CreateLinkForPaymentServiceUserreturns a public Link URL +attemptID. - Frontend redirect — the user picks a bank, authenticates, and lands on your
clientRedirectURL. - Item-creation webhook — the connector exchanges the
public_tokenfor anaccess_tokenand stores it on the connection.
Step-by-step walkthrough on the Open Banking Getting Started guide.
Redirect URL requirements#
- HTTPS in production.
- Registered in the Plaid dashboard under Developers → API → Allowed redirect URIs before issuing a Link token targeting it. The connector doesn't auto-register; mismatches surface as
INVALID_OAUTH_STATE_PARAMETERat the Link step. - Mobile apps must use Plaid's universal-link patterns (iOS, Android). The connector returns the URL unchanged — universal-link routing is a frontend concern.
Asset model#
Multi-currency, formatted to UMN at ISO 4217 precision (USD/2, EUR/2, GBP/2). Amounts arrive as decimal strings; the connector applies major-to-minor scaling.
Status mapping#
Plaid transactions are non-stateful — Plaid surfaces them only after they post, so the connector emits each as SUCCEEDED. The pending flag maps to PENDING until Plaid clears it and the row's pending_transaction_id is replaced by a permanent transaction_id; at that point the connector swaps the reference and moves the row to SUCCEEDED.
Metadata keys#
Under com.plaid.spec/:
- Account:
account_id,mask(account-number last4),name,official_name,subtype(checking/savings/credit card/ …),verification_status. - External account:
account_number_last4,routing_number,wire_routing_number(where available). - Payment:
transaction_id,pending_transaction_id(whenpending=true),category,category_id,merchant_name,personal_finance_category.primary,payment_channel,iso_currency_code.
PSU-level metadata also carries user_token and link_token — connector internals, not editable.
Workflow tree#
FetchAccounts (periodic) — per PSU/Item
├── FetchBalances (FromPayload — no extra API call)
└── FetchPayments (periodic) — per PSU/Item, /transactions/sync cursor
FetchExternalAccounts (periodic)
CreateWebhooks — one hook per Item, provisioned automatically on linkPagination and recovery#
/transactions/sync is cursor-based and idempotent. The connector persists the latest cursor per Item in platform-managed State; restarts resume from the last committed cursor (Plaid's at-least-once semantics aside).
Known gaps#
- Identity and Income products aren't surfaced — only Auth, Transactions, and (when present) Transfer.
- Investment accounts are surfaced as accounts but their holdings are not — only depository balances and transactions land.
- Webhooks failing signature verification are dropped silently with a single-line log. Look for
plaid: webhook signature failed verification.