# Plaid

- Tool: Bubble
- Difficulty: Advanced
- Time required: 4–6 hours
- Last updated: July 2026

## TL;DR

Connect Bubble to Plaid using a two-phase architecture: a Bubble HTML element loads the Plaid Link widget for browser-side bank selection, and the API Connector with private client_id and secret headers handles all server-side token exchange and data fetching. The link_token must be created server-side before the widget opens, and the returned public_token must be exchanged server-side for a permanent access_token.

## Understanding Plaid's Two-Phase Flow in Bubble

Plaid is architecturally different from payment processors like Stripe: it does not process transactions — it reads existing bank account data. The security model is two-sided, and Bubble must handle both sides.

Phase 1 — The Browser (Plaid Link Widget): Your user needs to log in to their bank through Plaid's hosted interface. This is the Plaid Link widget — a JavaScript SDK that handles bank selection, credential entry, and OAuth redirects entirely within Plaid's own interface. In Bubble, you load this widget in an HTML element. It cannot be a native Bubble element — Plaid's widget is JavaScript-based and requires embedding a script tag.

However, before the Link widget can open, it needs a short-lived link_token. This token tells Plaid which user is linking a bank account, which products to enable, and which redirect URL to use. Critically, this token must be created by YOUR server calling Plaid's /link/token/create — never by the browser directly, because the call requires your secret key.

This is the most commonly skipped step in Bubble/Plaid tutorials: call /link/token/create from a Backend Workflow or page-load workflow first, store the link_token in a Bubble state, then pass it to the HTML element to initialize the widget.

Phase 2 — The Server (Token Exchange): When the user successfully links their bank through the widget, Plaid calls your onSuccess JavaScript callback with a public_token. This temporary token must be exchanged server-side via /item/public_token/exchange for a permanent access_token. The access_token is what your app uses to fetch balances, transactions, and identity data going forward.

In Bubble, you pass the public_token from the HTML element's JavaScript callback back into Bubble state using bubble_fn_trigger, then immediately trigger a server-side workflow that calls the exchange endpoint via API Connector.

The access_token is sensitive and permanent — it must be stored in your Bubble database with strict Privacy rules, never returned in a public-facing API response, and invalidated via /item/remove if the user disconnects their account.

## Before you start

- A Plaid account — sign up at dashboard.plaid.com. Sandbox access is free and immediate; Development access (up to 100 Items) requires email verification; Production requires Plaid's review and approval
- Your Plaid client_id and secret from the Plaid Dashboard — found under Team Settings → Keys
- A Bubble app on a paid plan (Starter $32/month or above) — required for Backend Workflows, which handle the server-side public_token exchange
- The Plaid sandbox test credentials for testing: institution = 'First Platypus Bank', username = 'user_good', password = 'pass_good'
- A basic understanding of Bubble HTML elements and custom states — the Plaid Link widget lives inside an HTML element and communicates with Bubble workflows through JavaScript

## Step-by-step guide

### 1. Create a Plaid App and Set Up API Connector with Private Credentials

Before building anything in Bubble, you need your Plaid credentials and a properly configured API Connector group that keeps those credentials server-side.

Log in to your Plaid account at dashboard.plaid.com. Go to Team Settings → Keys. You will see two key types:
- client_id: your unique application identifier (not secret, but still keep private)
- sandbox_secret / development_secret / production_secret: your secret key for each environment

Note: Plaid uses separate secrets per environment, not a single key with a test/live mode toggle like Stripe. You will start with the sandbox secret and replace it with development or production secret when you move environments.

In Bubble, click Plugins → Add plugins → search 'API Connector' → Install (it is free, by Bubble).

Click 'Add another API' and name the group 'Plaid'. In the shared headers section, add two headers — both marked Private:
- Header 1: PLAID-CLIENT-ID / value: your_client_id (check Private)
- Header 2: PLAID-SECRET / value: your_sandbox_secret (check Private)

Note that Plaid uses custom header names instead of a standard Authorization header — this is correct for Plaid's API. All calls in this API Connector group will automatically include both headers on every request, running server-side with no browser exposure.

Also add a shared header Content-Type with value application/json (not Private — this is safe to expose).

```
{
  "api_group": "Plaid",
  "shared_headers": [
    {
      "key": "PLAID-CLIENT-ID",
      "value": "your_client_id_here",
      "private": true
    },
    {
      "key": "PLAID-SECRET",
      "value": "your_sandbox_secret_here",
      "private": true
    },
    {
      "key": "Content-Type",
      "value": "application/json",
      "private": false
    }
  ],
  "base_urls": {
    "sandbox": "https://sandbox.plaid.com",
    "development": "https://development.plaid.com",
    "production": "https://production.plaid.com"
  }
}
```

**Expected result:** A 'Plaid' API Connector group exists with two Private headers (PLAID-CLIENT-ID and PLAID-SECRET). No secret value is visible in Bubble's frontend.

### 2. Add API Connector Calls for Token Operations

Add four API Connector calls inside the Plaid group. Each call handles a different step of the Plaid flow. You must initialize each call with a real test response before Bubble can detect the response fields.

Call 1 — /link/token/create (POST):
URL: https://sandbox.plaid.com/link/token/create
Method: POST
Use as: Action
Body (JSON): client_name, user.client_user_id, products, country_codes, language, redirect_uri (optional). The client_user_id should be a unique identifier for the current Bubble user — map this dynamically as 'Current User's unique id'.

For initialization, paste this sample body:
{
  "client_name": "My App",
  "user": { "client_user_id": "test_user_123" },
  "products": ["transactions"],
  "country_codes": ["US"],
  "language": "en"
}
Click Initialize. Plaid returns a link_token — Bubble detects this field.

Call 2 — /item/public_token/exchange (POST):
URL: https://sandbox.plaid.com/item/public_token/exchange
Method: POST
Use as: Action
Body: public_token (dynamic — will be passed from the Link widget callback).
For initialization, use a test public_token: 'public-sandbox-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'. Plaid will return access_token and item_id.

Call 3 — /accounts/balance/get (POST):
URL: https://sandbox.plaid.com/accounts/balance/get
Method: POST
Use as: Data (returns a list of accounts)
Body: access_token (dynamic).
Initialize with a sandbox access_token to get Bubble to detect the accounts array and balance fields.

Call 4 — /transactions/sync (POST):
URL: https://sandbox.plaid.com/transactions/sync
Method: POST
Use as: Data
Body: access_token (dynamic), cursor (optional, for pagination).

```
// Call 1: POST /link/token/create
{
  "client_name": "My Bubble App",
  "user": {
    "client_user_id": "<Current User's unique id>"
  },
  "products": ["transactions"],
  "country_codes": ["US"],
  "language": "en"
}
// Response: { "link_token": "link-sandbox-abc123...", "expiration": "2024-...", "request_id": "..." }

// Call 2: POST /item/public_token/exchange
{
  "public_token": "<public_token from Link widget>"
}
// Response: { "access_token": "access-sandbox-xyz...", "item_id": "JxLy...", "request_id": "..." }

// Call 3: POST /accounts/balance/get
{
  "access_token": "<stored access_token>"
}
// Response: { "accounts": [{"account_id": "...", "balances": {"current": 1234.56}, "name": "Checking", ...}] }

// Call 4: POST /transactions/sync
{
  "access_token": "<stored access_token>",
  "cursor": ""
}
// Response: { "added": [{"transaction_id": "...", "amount": 25.00, "name": "Starbucks", ...}] }
```

**Expected result:** Four API Connector calls are configured and initialized inside the Plaid group. Bubble has detected response fields for each, including link_token, access_token, item_id, and the accounts array structure.

### 3. Create the Bubble Database Types and Privacy Rules

Plaid's access_token is a permanent, sensitive credential — it authorizes your app to read a user's bank account indefinitely until revoked. You must store it securely in Bubble's database with strict Privacy rules.

Go to the Data tab in your Bubble editor. Create a new data type called 'Plaid Item' with these fields:
- access_token (type: text) — the permanent access token from Plaid
- item_id (type: text) — Plaid's identifier for the bank connection
- institution_name (type: text) — the bank's name (for display)
- user (type: User) — the Bubble user who owns this connection
- connected_at (type: date) — when the bank was linked
- is_active (type: yes/no, default: yes) — set to no if the user disconnects

Now set up Privacy rules for this data type. Go to Data → Privacy → Plaid Item. Add these rules:
- 'Find this in searches': Only allow when 'This Plaid Item's user = Current User'
- 'View all fields': Restrict to 'This Plaid Item's user = Current User'

This ensures that even if a Repeating Group queries all Plaid Items, each user only sees their own. Most critically, access_token values will never appear in other users' browser data.

For displaying account information without storing sensitive details, create a second data type 'Connected Account' with fields:
- plaid_account_id (text) — Plaid's account_id
- account_name (text) — display name (e.g., 'Chase Checking')
- account_type (text) — checking, savings, credit
- plaid_item (Plaid Item) — reference to the parent connection
- user (User) — for direct privacy rule enforcement

This separation means your app can display account names and types (low sensitivity) while the access_token remains locked in the more restricted Plaid Item type.

```
// Bubble Data Types:

// Plaid Item:
// access_token     → Text (SENSITIVE — strict privacy rules required)
// item_id          → Text
// institution_name → Text
// user             → User
// connected_at     → Date
// is_active        → Yes/No (default: Yes)

// Connected Account:
// plaid_account_id → Text
// account_name     → Text
// account_type     → Text  (checking / savings / credit)
// plaid_item       → Plaid Item
// user             → User

// Privacy Rules → Plaid Item:
// Find in searches: This Plaid Item's user = Current User
// View all fields: This Plaid Item's user = Current User

// Privacy Rules → Connected Account:
// Find in searches: This Connected Account's user = Current User
```

**Expected result:** The Plaid Item and Connected Account data types are created. Privacy rules prevent any user from viewing or searching another user's Plaid connection records.

### 4. Embed the Plaid Link Widget and Create the Token Exchange Workflow

This step handles the browser-side: embedding the Plaid Link JavaScript widget in a Bubble HTML element, initializing it with a link_token, capturing the public_token from the user's bank selection, and passing it back to Bubble for server-side exchange.

First, create the link_token. Add a page-load workflow (trigger: Page is loaded) with one action: call 'Plaid - /link/token/create'. Pass 'Current User's unique id' as the user.client_user_id parameter. Store the result in a Custom State called 'link_token' (type: text) on the page or on a group element.

Now add a Bubble HTML element to your page. In the HTML field, paste the following code. Replace [Your App Name] and the state reference as appropriate:

The widget's onSuccess callback receives the public_token. Since plain JavaScript inside an HTML element cannot directly call Bubble workflow actions, you use Bubble's bridge: bubble_fn_trigger(). This function, provided by Bubble, can pass data back into Bubble's state system.

Create a Bubble Custom State on a Group element (or the page) called 'public_token' (type: text). In the HTML element's onSuccess callback, call: bubble_fn_public_token(public_token) — where 'public_token' matches the state name you chose prefixed with 'bubble_fn_'.

Then create a Workflow triggered by 'When the Custom State public_token is not empty': call 'Plaid - /item/public_token/exchange' with the public_token state value. In the next action, create a new Plaid Item in the database with the returned access_token and item_id. Set user to Current User. Then clear the public_token state (set it back to empty) to reset the trigger.

This Backend Workflow can be on a paid plan, or you can use a triggered workflow on state change — state-change triggers work on all plans.

```
<!-- Bubble HTML Element: Plaid Link Widget -->
<div id="plaid-link-container">
  <button id="open-plaid" onclick="openPlaidLink()">Connect Bank Account</button>
</div>

<script src="https://cdn.plaid.com/link/v2/stable/link-initialize.js"></script>

<script>
  // The link_token is passed from Bubble state into a JS variable:
  // In Bubble's HTML element, use [Group's link_token state] in the value:
  var linkToken = '[Group PlaidContainer link_token state]';

  var handler;

  function openPlaidLink() {
    if (!handler) {
      handler = Plaid.create({
        token: linkToken,
        onSuccess: function(public_token, metadata) {
          // Pass public_token back into Bubble state:
          bubble_fn_public_token(public_token);
        },
        onExit: function(err, metadata) {
          if (err) {
            console.log('Plaid Link exited with error:', err);
          }
        }
      });
    }
    handler.open();
  }
</script>

// Bubble Workflow: When page's public_token state is not empty
// Step 1: Call Plaid - /item/public_token/exchange
//         public_token = Page's public_token state
// Step 2: Create a new Plaid Item
//         access_token = Result of Step 1's access_token
//         item_id = Result of Step 1's item_id
//         user = Current User
//         connected_at = Current date/time
//         is_active = yes
// Step 3: Set page's public_token state to empty (reset trigger)
```

**Expected result:** Your page loads, creates a link_token server-side, and passes it to the HTML element. Clicking 'Connect Bank Account' opens the Plaid Link widget. After the user connects their bank, the public_token flows back to Bubble, triggers the exchange workflow, and a Plaid Item record is created in your database.

### 5. Fetch and Display Bank Balances and Transactions

With the access_token stored in the database, you can now fetch and display real bank data. This step covers retrieving account balances and recent transactions and displaying them in Bubble Repeating Groups.

Create a page or section for the financial dashboard. Add a Repeating Group for accounts — this will show each linked bank account.

For the balance display: create a workflow triggered by a button click or page load. The workflow calls 'Plaid - /accounts/balance/get' with the access_token from the user's Plaid Item record (Search for Plaid Items where user = Current User: first item's access_token). The API Connector 'Use as Data' mode means Bubble will detect the accounts array and allow you to use 'Plaid - /accounts/balance/get's accounts' as the data source for a Repeating Group.

In the Repeating Group: set Type of content to 'Plaid accounts/balance/get accounts' (the auto-detected type from the API Connector). Data source: Plaid /accounts/balance/get's accounts. Display fields:
- Current cell's name (account name)
- Current cell's balances current (current balance as formatted number)
- Current cell's type (checking/savings/credit)

For transactions, the /transactions/sync endpoint returns three arrays: added (new transactions), modified (updated), and removed (deleted). For a simple implementation, call /transactions/sync on page load, store the 'added' array items into Bubble's database as Transaction records, then display them in a second Repeating Group sorted by date descending.

RapidDev's team has built complex Plaid-powered dashboards in Bubble — if you need custom transaction categorization or recurring payment detection, a free scoping call at rapidevelopers.com/contact can help you plan the data architecture.

```
// Repeating Group data source configuration:
// Type of content: Plaid /accounts/balance/get accounts (API-detected type)
// Data source: Run API workflow 'Plaid - Get Balances'
//              → triggers GET /accounts/balance/get
//              → returns accounts list

// Repeating Group cell fields to display:
// Text element 1: Current cell's name
// Text element 2: Current cell's balances current (format as currency)
// Text element 3: Current cell's type

// For Transactions - create a 'Transaction' data type:
// transaction_id   → Text
// merchant_name    → Text
// amount           → Number
// date             → Date
// category         → Text
// plaid_item       → Plaid Item
// user             → User

// Workflow: On page load → call /transactions/sync
// For each item in 'added' array:
//   Create Transaction record with transaction_id, merchant_name, amount, date
```

**Expected result:** Your dashboard Repeating Group shows the user's connected bank accounts with live balances. A transactions Repeating Group displays recent transactions fetched from Plaid and stored in Bubble's database.

### 6. Handle Bank Disconnection and Token Revocation

When a user disconnects their bank account, you must invalidate the access_token with Plaid AND delete or deactivate the record in your Bubble database. Leaving orphaned access_tokens is a security liability.

Add a 'Disconnect Bank' button in your app. The workflow should:
1. Call Plaid's /item/remove endpoint via API Connector (POST with access_token in the body). This tells Plaid to revoke the token on their side.
2. Make changes to the Plaid Item record: set is_active to no (or delete it entirely).
3. Delete any associated Connected Account records for this Plaid Item.
4. Show a confirmation message to the user.

Add a new API Connector call: name it 'Remove Item', URL https://sandbox.plaid.com/item/remove, Method POST, body param: access_token (dynamic).

Plaid also sends webhooks when an Item's status changes — for example, when a bank revokes access (ITEM.ERROR.ITEM_LOGIN_REQUIRED). Add a Backend Workflow endpoint to receive these events (paid Bubble plan required). When Plaid sends an ITEM.ERROR webhook, mark the affected Plaid Item as inactive in Bubble and send the user an email prompting them to reconnect their bank.

Note for Plaid webhook registration: go to your Plaid Dashboard → Activity → Webhooks and register your Bubble Backend Workflow URL. Plaid does not verify webhook authenticity with a signature in the same way as Stripe — use a query parameter secret in your URL for basic validation.

```
// Call 5: POST /item/remove
{
  "access_token": "<stored access_token>"
}
// Response: { "request_id": "...", "removed": true }

// Workflow: 'Disconnect Bank' button click
// Step 1: Call Plaid - /item/remove
//         access_token = Current User's Plaid Item's access_token
// Step 2: Make changes to Current User's Plaid Item
//         is_active = no
// Step 3: Delete Connected Account(s) where plaid_item = Current User's Plaid Item
// Step 4: Show confirmation alert 'Bank account disconnected'

// Backend Workflow: receive Plaid ITEM.ERROR webhook
// Detect fields: webhook_code, item_id
// When webhook_code = 'ITEM_LOGIN_REQUIRED':
//   Find Plaid Item where item_id = detected item_id
//   Set is_active = no
//   Send email to Plaid Item's user: 'Your bank connection needs to be refreshed'
```

**Expected result:** Clicking 'Disconnect Bank' revokes the access_token with Plaid, marks the Plaid Item as inactive in Bubble's database, and cleans up associated records. Plaid webhooks update the status automatically when a bank connection expires.

## Best practices

- Always create the link_token server-side (via API Connector workflow) before loading the Plaid Link widget — never hardcode a token or reuse an expired one. link_tokens expire after 30 minutes.
- Store the access_token only in Bubble's database with strict Privacy rules. Never pass it through URL parameters, return it in public API responses, or display it in any Repeating Group accessible to other users.
- Call /item/remove when a user disconnects their bank — revoke the token with Plaid in addition to updating your Bubble database. Orphaned access_tokens are a security liability and may keep your API usage metrics artificially high.
- Use sandbox test credentials (user_good / pass_good) during development — never attempt real bank logins in sandbox mode, as Plaid's sandbox returns predictable test data from a fixed institution.
- Refresh link_tokens on demand, not on every page load — creating a link_token only when the user clicks 'Connect Bank' reduces unnecessary API calls against your Plaid plan limits.
- Monitor your Plaid 'Items' count in the Plaid Dashboard. The Development tier allows up to 100 live Items (bank connections). Exceeding this requires a Production upgrade, which requires Plaid's approval.
- Add error handling in your HTML element's onExit callback — log Plaid Link exit events (user cancelled, bank not found) and provide a clear retry path for users who drop off during the connection flow.
- For the /transactions/sync endpoint, store the cursor value returned by Plaid and pass it on subsequent calls to retrieve only new transactions rather than re-fetching the entire transaction history on every sync.

## Use cases

### Budget Tracker and Personal Finance Dashboard

Users connect their bank accounts through Plaid Link, and the app displays a spending dashboard with categorized transactions, monthly trends, and account balances. All data is fetched from Plaid's /transactions/sync endpoint and stored in Bubble for display in charts and Repeating Groups.

Prompt example:

```
Build a Bubble Repeating Group that displays the last 30 days of transactions from Plaid's /transactions/sync API, grouped by the personal_finance_category field, with each row showing the merchant name, amount, and date.
```

### Income Verification for Lending Apps

A lending platform requires applicants to verify their income by connecting their primary bank account. Plaid's Income product pulls recurring deposit data, and Bubble stores the verified income amount alongside the loan application record for underwriter review.

Prompt example:

```
Create a Bubble workflow that calls Plaid's /income/verification/paystubs/get endpoint with the user's access_token, extracts the employer name and monthly gross income, and saves them to the Loan Application data type with an income_verified boolean field set to yes.
```

### ACH Payment Source Linking

An e-commerce or subscription app lets users pay via bank transfer (ACH) rather than card. Plaid Auth verifies the user's account and routing numbers, which are then passed to Stripe ACH or Dwolla to initiate transfers — giving users a cheaper payment option than credit card.

Prompt example:

```
Set up a Bubble workflow that calls Plaid's /auth/get endpoint with the user's access_token, retrieves the account_id plus routing and account numbers, stores them encrypted in Bubble, and passes them to a Stripe bank account verification API call.
```

## Troubleshooting

### The Plaid Link widget shows 'Invalid link_token' or fails to open

Cause: The link_token was not successfully created before the widget initialized, or the link_token has expired (it is valid for 30 minutes). The page-load workflow may have failed to call /link/token/create, or the state was not passed correctly to the HTML element.

Solution: Add the Bubble Debugger to your page and check whether the page-load workflow completed successfully and set the link_token state. Look at the API Connector logs in Bubble's Logs tab to see if the /link/token/create call returned a successful response. Also verify that the HTML element reads the state correctly — in Bubble's HTML editor, the state reference should look like '[Group PlaidContainer's link_token state]'. Missing the user.client_user_id parameter in the /link/token/create body causes a 400 error.

```
// In the HTML element, verify the token initialization:
var linkToken = '[Group PlaidContainer link_token state]';
// If linkToken is empty or undefined, the state was not set correctly.
// Check the page-load workflow completed and set the Custom State.
```

### The public_token is not passed back to Bubble after the user links their bank

Cause: The bubble_fn_ bridge function name does not match the Custom State name exactly, or the Toolbox plugin required for JavaScript-to-Bubble communication is missing.

Solution: Verify the Custom State name on your element. If the state is named 'public_token', the JavaScript call must be bubble_fn_public_token(value) — not bubble_fn_publicToken or any other variation. Also, Bubble's bubble_fn_ bridge is available without the Toolbox plugin for Custom States, but if you are using the Toolbox plugin's 'Run JavaScript' action instead of an HTML element, you may need to install and configure it separately.

### API Connector call returns 400 with 'INVALID_FIELD: user.client_user_id is required'

Cause: The /link/token/create call body is missing the user.client_user_id field. Plaid requires a unique identifier for each user initiating a Link session.

Solution: In your API Connector call configuration for /link/token/create, add a body parameter with key 'user.client_user_id' and value set to 'Current User's unique id' (Bubble's built-in unique identifier for each User record). This tells Plaid which user is linking the bank, so if the same user links multiple accounts, they share the same client_user_id.

```
// Body parameters for /link/token/create:
// key: client_name    value: My App
// key: user.client_user_id   value: [Current User's unique id]
// key: products       value: ["transactions"]
// key: country_codes  value: ["US"]
// key: language       value: en
```

### 'There was an issue setting up your call' when initializing the /accounts/balance/get call

Cause: Initialization requires a real sandbox access_token. The default test value may not work — Plaid's balance endpoint requires an access_token from an Item that actually has accounts linked.

Solution: Complete the Link widget flow first (link a test bank account using sandbox test credentials: user_good / pass_good at First Platypus Bank). This creates a real sandbox Item with a real access_token. Copy that access_token from your Bubble database and use it as the test value when initializing the /accounts/balance/get call in the API Connector.

### Backend Workflow for token exchange not available — 'Workflow API is not enabled'

Cause: Backend Workflows require a paid Bubble plan. Free plan apps cannot create API Workflows.

Solution: Upgrade to a Bubble paid plan to unlock Backend Workflows. Alternatively, you can trigger the token exchange from a state-change triggered workflow (available on all plans): create a triggered workflow that fires 'When the Page's public_token is not empty' and calls the /item/public_token/exchange action. State-change triggers are available on the Free plan, though timing can be slightly less reliable than a server-side Backend Workflow.

## Frequently asked questions

### Do I need a paid Bubble plan to use Plaid?

For outbound API calls (creating link tokens, fetching balances, fetching transactions), the Free plan technically works. However, the server-side public_token exchange is most reliable in a Backend Workflow, which requires a paid Bubble plan (Starter $32/month). A workaround on the Free plan is using a state-change triggered workflow, but timing of the exchange can be unreliable since state triggers depend on the client session.

### How does Plaid's Development vs Production access work?

Sandbox is free and gives you predictable test data with test credentials. Development access (also free) lets you connect up to 100 real bank accounts for testing — useful for verifying real institution connections. Production requires a review by Plaid before you can access live user bank data at scale. Apply for Development access early in your build process so you are not waiting for approval at launch time.

### Can I use Plaid without a paid Plaid account?

Yes — Plaid's Sandbox environment is free with no time limit. The Development tier (100 real Items) is also free. You only pay for Production access, where Plaid charges per Item per month based on which products you use. Pricing varies by product (Transactions, Auth, Identity, Income) and is negotiated with Plaid's sales team — there is no public per-Item rate card.

### What happens if a user's bank login expires or changes their password?

Plaid sends a webhook event ITEM.ERROR with error_code ITEM_LOGIN_REQUIRED. Your Backend Workflow should catch this, mark the Plaid Item as inactive in Bubble, and notify the user to reconnect their bank. The user must go through the Plaid Link widget again (with a fresh link_token) to re-authenticate — you cannot re-authorize silently in the background.

### Is my users' bank data stored by Plaid or by my Bubble app?

Plaid stores the authentication credentials and the raw bank data temporarily on their servers. Your Bubble app stores the access_token (which unlocks future data requests) and any transaction/balance data you explicitly save to your Bubble database. You are responsible for your Bubble database Privacy rules — Plaid is responsible for their own data security. Both your Plaid developer agreement and your privacy policy should disclose the data sharing arrangement to users.

---

Source: https://www.rapidevelopers.com/bubble-integrations/plaid
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/plaid
