Skip to main content
Plaid integration allows your customers to securely connect their bank accounts without manually entering account numbers and routing information. Grid handles the complete Plaid Link flow, automatically creating external accounts when customers authenticate their banks.
Plaid integration requires Grid to manage your Plaid configuration. Contact support to enable Plaid for your platform.

Overview

The Plaid flow involves collaboration between your platform, Grid, Plaid, and the customer’s bank:
  1. Request link token: Your platform requests a Plaid Link token from Grid for a specific customer
  2. Initialize Plaid Link: Display Plaid Link UI to your customer using the link token
  3. Customer authenticates: Customer selects their bank and authenticates using Plaid Link
  4. Exchange tokens: Plaid returns a public token; your platform sends it to Grid’s callback URL
  5. Async processing: Grid exchanges the public token with Plaid and retrieves account details
  6. External account created: Grid creates the external account and sends a webhook notification. The external account is available for transfers and payments
To initiate the Plaid flow, request a link token from Grid:
curl -X POST 'https://api.lightspark.com/grid/2025-10-13/plaid/link-tokens' \
  -H 'Authorization: Basic $GRID_CLIENT_ID:$GRID_CLIENT_SECRET' \
  -H 'Content-Type: application/json' \
  -d '{
    "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001"
  }'
Response:
{
  "linkToken": "link-sandbox-af1a0311-da53-4636-b754-dd15cc058176",
  "expiration": "2025-10-05T18:30:00Z",
  "callbackUrl": "https://api.lightspark.com/grid/2025-10-13/plaid/callback/link-sandbox-af1a0311-da53-4636-b754-dd15cc058176",
  "requestId": "req_abc123def456"
}
Store the callbackUrl when you request the link token so you can retrieve it later when exchanging the public token.

Key response fields:

  • linkToken: Use this to initialize Plaid Link in your frontend
  • callbackUrl: Where to POST the public token after Plaid authentication completes. The URL follows the pattern https://api.lightspark.com/grid/{version}/plaid/callback/{linkToken}. While you can construct this manually, we recommend using the provided URL for forward compatibility.
  • expiration: Link tokens typically expire after 4 hours
  • requestId: Unique identifier for debugging purposes
Link tokens are single-use and will expire. If the customer doesn’t complete the flow, you’ll need to request a new link token.
Display the Plaid Link UI to your customer using the link token. The implementation varies by platform:
Install the appropriate Plaid SDK for your platform:
  • React: npm install react-plaid-link
  • React Native: npm install react-native-plaid-link-sdk
  • Vanilla JS: Include the Plaid script tag as shown above
  • React
  • React Native
  • Vanilla JavaScript
import { usePlaidLink } from 'react-plaid-link';

function BankAccountConnector({ linkToken, onSuccess }) {
const { open, ready } = usePlaidLink({
token: linkToken,
onSuccess: async (publicToken, metadata) => {
console.log('Plaid authentication successful');

      // Send public token to YOUR backend endpoint
      await fetch('/api/plaid/exchange-token', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          publicToken: publicToken,
          accountId: metadata.account_id, // Optional
        }),
      });

      onSuccess();
    },
    onExit: (error, metadata) => {
      if (error) {
        console.error('Plaid Link error:', error);
      }
      console.log('User exited Plaid Link');
    },

});

return (

<button onClick={() => open()} disabled={!ready}>
  Connect your bank account
</button>
); }

Exchange the public token on your backend

Create a backend endpoint that receives the public token from your frontend and forwards it to Grid’s callback URL:
// Backend endpoint: POST /api/plaid/exchange-token
app.post('/api/plaid/exchange-token', async (req, res) => {
  const { publicToken, accountId } = req.body;
  const customerId = req.user.gridCustomerId; // From your auth

  try {
    // Get the callback URL (you stored this when requesting the link token)
    const callbackUrl = await getStoredCallbackUrl(customerId);

    // Forward to Grid's callback URL with proper authentication
    const response = await fetch(callbackUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        publicToken: publicToken,
        accountId: accountId,
      }),
    });

    if (!response.ok) {
      throw new Error(`Grid API error: ${response.status}`);
    }

    const result = await response.json();
    res.json({ success: true, message: result.message });
  } catch (error) {
    console.error('Error exchanging token:', error);
    res.status(500).json({ error: 'Failed to process bank account' });
  }
});
Response from Grid (HTTP 202 Accepted):
{
  "message": "External account creation initiated. You will receive a webhook notification when complete.",
  "requestId": "req_def456ghi789"
}
A 202 Accepted response indicates Grid has received the token and is processing it asynchronously. The external account will be created in the background.

Handle webhook notification

After Grid creates the external account, you’ll receive an ACCOUNT_STATUS webhook.
{
  "type": "ACCOUNT_STATUS",
  "timestamp": "2025-01-15T14:32:10Z",
  "webhookId": "Webhook:019542f5-b3e7-1d02-0000-0000000000ac",
  "customerId": "Customer:019542f5-b3e7-1d02-0000-000000000001",
  "account": {
    "accountId": "ExternalAccount:a12dcbd6-dced-4ec4-b756-3c3a9ea3d123",
    "status": "ACTIVE",
    "currency": "USD",
    "platformAccountId": "user_123_primary_bank",
    "accountInfo": {
      "accountType": "US_ACCOUNT",
      "accountNumber": "123456789",
      "routingNumber": "021000021",
      "accountCategory": "CHECKING",
      "bankName": "Chase Bank",
      "beneficiary": {
        "beneficiaryType": "INDIVIDUAL",
        "fullName": "John Doe",
        "birthDate": "1990-01-15",
        "nationality": "US",
        "address": {
          "line1": "123 Main Street",
          "city": "San Francisco",
          "state": "CA",
          "postalCode": "94105",
          "country": "US"
        }
      }
    }
  }
}

Error handling

Handle common error scenarios:
const { open } = usePlaidLink({
  token: linkToken,
  onExit: (error, metadata) => {
    if (error) {
      console.error("Plaid error:", error);
      // Show user-friendly error message
      setError("Unable to connect to your bank. Please try again.");
    } else {
      // User closed the modal without completing
      console.log("User exited without connecting");
    }
  },
});
I