The Plaid connector links Formance to Plaid's bank-aggregation platform. Once installed, it lets your stack drive the Open Banking PSU flow — create a Payment Service User, forward them to Plaid, hand them a Plaid Link session, and once they've authenticated their bank, sync the resulting accounts, balances, and transactions back through Connectivity.
The connector first ships in Payments 3.2.0.
Prerequisites#
You need a Plaid account and a clientID + clientSecret pair
dedicated to Formance. The connector uses Plaid's
/link/token/create and /transactions/sync endpoints, so the key
needs access to Auth, Transactions, and Identity at minimum (set the
products on the Plaid dashboard before issuing the key).
For inbound webhook deliveries, Plaid signs every webhook with a JWT that the connector verifies against Plaid's public key — there is 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 the connector to Plaid's sandbox host
(https://sandbox.plaid.com); production uses
https://production.plaid.com. There is no separate "development"
environment in this connector.
Capabilities#
FETCH_ACCOUNTS— depository, credit, and loan accounts attached to each PSU's connections.FETCH_BALANCES— available + current balance per account, 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— at install time the connector creates webhooks per PSU's Item (Plaid's connection primitive) so transactions and account changes flow back in real time.
The connector does not initiate transfers or payouts — Plaid's
Transfer product is read-only here. Outbound flows belong on a
PSP connector (Stripe, Increase, etc.).
Linking a user#
Plaid's authentication ceremony runs through Plaid Link. The Connectivity surface is:
- Create a PSU —
v3CreatePaymentServiceUser. Returns a PSU ID. - Forward the PSU to Plaid —
v3ForwardPaymentServiceUserToProvider. The connector calls/link/token/createand stores the resultinglink_tokenas PSU metadata. - Create a Link session —
v3CreateLinkForPaymentServiceUser. Returns a public Link URL plus anattemptID. - Frontend redirects the user to the Link URL. They pick their bank, authenticate, and Plaid redirects them back to your
clientRedirectURL. - Plaid posts an Item-creation webhook to the connector. The connector exchanges the
public_tokenfor anaccess_tokenand stores it against the connection.
The full step-by-step walkthrough is on the Open Banking Getting Started guide.
Redirect URL requirements#
- Must be HTTPS in production.
- Must be registered in the Plaid dashboard under Developers →
API → Allowed redirect URIs before issuing a Link token that
targets it. The connector does not auto-register; mismatches surface
as
INVALID_OAUTH_STATE_PARAMETERerrors at the Link step. - Mobile apps must use Plaid's universal link patterns (see Plaid's iOS and Android guides). The connector returns the link URL unchanged — universal-link routing is a frontend concern.
Asset model#
Plaid is multi-currency; the connector formats balances and payments
to UMN with the precision per ISO 4217 (USD/2, EUR/2, GBP/2).
Amounts are decimal strings; the connector applies major-to-minor
scaling before emission.
Status mapping#
Plaid transactions are non-stateful (Plaid surfaces them only after
they post). The connector emits each transaction as SUCCEEDED. The
pending flag from Plaid surfaces as 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#
Plaid-specific fields land 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 — these
are 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#
Plaid's /transactions/sync is cursor-based and idempotent. The
connector persists the latest cursor per Item in the platform-managed
State; restarts resume from the last committed cursor with no
replays beyond what Plaid itself may emit.
Known gaps#
- Identity and Income products aren't surfaced — the connector consumes 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 that fail signature verification are dropped silently
with a single-line log entry. Look for
plaid: webhook signature failed verificationin the worker logs if events seem to be missed.