Pay
Digital Wallets
Plaid

Plaid Integration

Accept instant bank payments with bank account verification


Plaid allows customers to connect their bank accounts instantly by logging into their bank through Plaid Link. Instead of manually entering routing and account numbers, customers authenticate with their online banking credentials and select their account. Plaid verifies the account in real-time, eliminating the delays and friction of traditional micro-deposit verification.

When to Use Plaid


Use Plaid when you want to enable bank payments with instant bank account verification. Plaid is ideal for reducing checkout friction, enabling immediate bank processing, and improving conversion rates for bank payment flows.

Benefits of Plaid

  • Enhanced Security: Uses bank-level authentication with OAuth and MFA
  • Better User Experience: No need to find checks or bank statements for account numbers
  • Reduced Errors: Eliminates typos and invalid account numbers from manual entry
  • Balance Check: Verify sufficient funds before processing to reduce NSF returns
  • Higher Success Rates: Verified accounts have lower return rates than manual entry

Common use cases

  • Subscription Payments: Enable instant recurring bank billing setup
  • Large Transactions: Accept high-value payments with lower fees than card processing
  • Instant Payouts: Verify bank accounts for payout disbursements
  • Wallet Funding: Enable customers to fund wallets from bank accounts

Plaid Availability


Plaid is available for all Payload accounts and works across browsers and platforms.

Supported Banks

Plaid supports over 12,000 financial institutions in the United States and Canada, including:

  • Major banks (Chase, Bank of America, Wells Fargo, etc.)
  • Regional and community banks
  • Credit unions
  • Online banks (Chime, Ally, etc.)

Checking Availability

The SDK provides a callback to detect if Plaid is available:

/** @type {HTMLElement | null} */
const plaidBtn = document.getElementById('plaid-button')
 
if (plaidBtn) {
  form.plaid(
    (plaidBtn)
  )
}

Integrating Plaid


Process bank payments immediately

Use Plaid with the Payment Form SDK to process bank payments immediately. This is ideal for checkout flows where customers pay with their bank account.

import payload
pl = payload.Session('secret_key_3bW9...', api_version='v2')
 
from flask import Flask, jsonify
 
app = Flask(__name__)
 
@app.route('/payment-form-intent', methods=['POST'])
def create_payment_intent():
    # Create a payment form intent
    intent = pl.Intent.create(
        type='payment_form',
        payment_form={
            'payment_template': {
                'amount': 100.00,
                'description': 'Order #12345',
                'receiver': {
                    'account_id': 'acct_receiver123'
                }
            }
        }
    )
 
    # Send the token to your frontend
    return jsonify({'token': intent.token})
<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payment-form" pl-form="payment">
    <label>Amount</label>
    <input type="number" pl-input="amount" value="100.00" required />
 
    <label>Description</label>
    <input type="text" pl-input="description" value="Purchase" required />
 
    <label>Account Holder Name</label>
    <input type="text" pl-input="account_holder" required />
 
    <button type="button" id="plaid-button">
      Pay with Bank Account via Plaid
    </button>
  </form>
 
  <script src="plaid.js"></script>
</body>
</html>
// Fetch client token from your server
fetch('/payment-form-intent', { method: 'POST' })
  .then(res => res.json())
  .then(data => {
    // Initialize Payload with the client token
    Payload(data.token)
 
    // Initialize the payment form
    const form = new Payload.Form(document.getElementById('payment-form'))
 
    // Get the Plaid button element
    const plaidButton = document.getElementById('plaid-button')
 
    // Activate Plaid on the button
    form.plaid(plaidButton)
 
    // Handle successful payment
    form.on('processed', (evt) => {
      console.log('Payment processed:', evt.transaction_id)
      window.location.href = `/payment/success?txn=${evt.transaction_id}`
    })
 
    // Handle declined payment
    form.on('declined', (evt) => {
      console.error('Payment declined:', evt.message)
      alert('Payment declined. Please try a different payment method.')
    })
 
    // Handle errors
    form.on('error', (evt) => {
      console.error('Error:', evt.message)
      alert('Payment failed: ' + evt.message)
    })
  })

This example shows the complete flow for accepting Plaid payments:

  1. Backend: Create an intent with type='payment_form' and configure payment amount
  2. Frontend HTML: Add a button with pl-plaid attribute
  3. Frontend JavaScript: Initialize the form and activate Plaid with .plaid()
  4. Event Handling: Listen for processed, authorized, or declined events

When the customer clicks the Plaid button:

  1. Plaid Link opens with a bank search interface
  2. Customer selects their bank and logs in with banking credentials
  3. Customer selects the account to use for payment
  4. Account is verified and payment is processed immediately
  5. The processed event fires with the transaction ID

Save bank accounts for future use

Use Plaid with the Payment Method Form SDK to save bank accounts for future use. This is ideal for subscription signups, wallet management, and recurring billing setup.

import payload
from flask import Flask, jsonify
 
pl = payload.Session('secret_key_3bW9...', api_version='v2')
 
app = Flask(__name__)
 
 
@app.route('/create-payment-method-intent', methods=['POST'])
def create_intent():
    # Create an intent for a payment method form
    intent = pl.Intent.create(
        type="payment_method_form",
        payment_method_form={
            "payment_method_template": {
                "account_id": "acct_customer123",
                # Optionally configure defaults
                'transfer_type': 'two_way',
            }
        },
    )
 
    # Return the client token to the frontend
    return jsonify({'client_token': intent.token})
 
 
 
if __name__ == "__main__":
    app.run(port=3000)
<!DOCTYPE html>
<html>
<head>
  <script src="https://payload.com/Payload.js"></script>
</head>
<body>
  <form id="payment-method-form" pl-form="payment_method">
    <label>Account Holder Name</label>
    <input type="text" pl-input="account_holder" required />
 
    <button type="button" id="plaid-button">
      Connect Bank Account via Plaid
    </button>
  </form>
 
  <script src="plaid.js"></script>
</body>
</html>
// Fetch client token from your server
fetch('/payment-method-form-intent', { method: 'POST' })
  .then(res => res.json())
  .then(data => {
    // Initialize Payload with the client token
    Payload(data.token)
 
    // Initialize the payment method form
    const form = new Payload.Form(document.getElementById('payment-method-form'))
 
    // Get the Plaid button element
    const plaidButton = document.getElementById('plaid-button')
 
    // Activate Plaid on the button
    form.plaid(plaidButton)
 
    // Handle successful payment method creation
    form.on('success', (evt) => {
      console.log('Bank account connected:', evt.payment_method_id)
      window.location.href = '/account/payment-methods?success=true'
    })
 
    // Handle errors
    form.on('error', (evt) => {
      console.error('Error:', evt.message)
      alert('Failed to connect bank account: ' + evt.message)
    })
  })

This example shows the complete flow for connecting bank accounts with Plaid:

  1. Backend: Create an intent with type='payment_method_form'
  2. Frontend HTML: Add a button with pl-plaid attribute
  3. Frontend JavaScript: Initialize the form and activate Plaid with .plaid()
  4. Event Handling: Listen for success or error events

When the customer clicks the Plaid button:

  1. Plaid Link opens with a bank search interface
  2. Customer selects their bank and logs in
  3. Customer selects the account to connect
  4. Account is verified and saved as a payment method
  5. The success event fires with the payment method ID

Setting Account Defaults

Configure the connected bank account as the default for automatic billing:

# On your backend
intent = pl.Intent.create(
    type="payment_method_form",
    payment_method_form={
        "payment_method_template": {
            "account_id": "acct_abc123",
            "bank_account": {
                "currency": "USD"  # USD or CAD
            },
            "account_defaults": {
                "paying": "payments"  # Make this the default payment method
            }
        }
    }
)

Styling the Plaid Button


You can customize the Plaid button appearance to match your design.

Standard Plaid Button

Use Plaid's standard button style:

<button pl-plaid class="plaid-button">
  <img src="/plaid-logo.svg" alt="Plaid" />
  <span>Connect with Plaid</span>
</button>
.plaid-button {
  background-color: #000;
  color: #fff;
  border: none;
  border-radius: 4px;
  padding: 12px 24px;
  font-size: 16px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  transition: background-color 0.2s;
}
 
.plaid-button:hover {
  background-color: #333;
}
 
.plaid-button img {
  height: 20px;
}

Bank Account Theme

Create a bank-themed button:

<button pl-plaid class="bank-button">
  <svg class="bank-icon" viewBox="0 0 24 24">
    <path d="M12 3L1 9l11 6 9-4.91V17h2V9M5 13.18v4L12 21l7-3.82v-4L12 17l-7-3.82z" />
  </svg>
  <span>Pay with Bank Account</span>
</button>
.bank-button {
  background-color: #0066cc;
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 14px 28px;
  font-size: 16px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
 
.bank-button:hover {
  background-color: #0052a3;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
 
.bank-icon {
  width: 24px;
  height: 24px;
  fill: currentColor;
}

Handling Events


The Payment Form SDK emits events for payment processing and connection status.

Success Events

form.on('success', (evt) => {
  console.log('Payment processed:', evt.transaction_id)
  if (evt.payment_method_id) {
    console.log('Bank account connected:', evt.payment_method_id)
  }
  window.location.href = `/success?txn=${evt.transaction_id}`
})

Error Events

form.on('error', (evt) => {
  console.error('Error:', evt.message)
  alert(`Failed to connect bank account: ${evt.message}`)
})
 
form.on('declined', (evt) => {
  console.error('Payment declined:', evt.message)
  alert('Your payment was declined. Please try another bank account or payment method.')
})

Testing Plaid Integration


Testing Plaid requires using Plaid's test mode with special test credentials.

Using Plaid Test Credentials

When using Payload in test mode, Plaid automatically operates in sandbox mode. Use these test credentials:

Testing Different Scenarios

Test various scenarios using different credentials:

// Test successful connection
Username: user_good;
Password: pass_good;

// Test invalid credentials
Username: user_bad;
Password: pass_good;

// Test locked account
Username: user_locked;
Password: pass_good;

// Test MFA requirement
Username: user_mfa;
Password: pass_good;
// MFA Code: 1234

Testing Payment Processing

After connecting a test bank account:

  1. Create a test payment using the connected account
  2. Verify the transaction appears in Payload Dashboard
  3. Check that webhooks fire correctly
  4. Test refunds and voids with the test transaction

Troubleshooting


Common issues and solutions when integrating Plaid.

Plaid Button Not Appearing

Symptoms: The Plaid button doesn't appear on the page.

Solutions:

  • Check the availability callback is properly configured
  • Ensure the page is served over HTTPS
  • Verify the intent token is valid
  • Check browser console for JavaScript errors

Plaid Link Not Opening

Symptoms: Clicking the button doesn't open Plaid Link.

Solutions:

  • Verify the form is properly initialized with new Payload.Form()
  • Ensure .plaid() is called after form initialization
  • Check that the intent token is valid and not expired
  • Verify no JavaScript errors are preventing execution
  • Test in a different browser

Institution Connection Failing

Symptoms: Selected bank connection fails or returns errors.

Solutions:

  • Verify the customer is entering correct banking credentials
  • Check if the institution is experiencing outages (shown in Plaid Link)
  • Test with a different bank to isolate institution-specific issues
  • Review error details in the error event handler
  • Contact Plaid support for institution-specific problems

MFA Issues

Symptoms: Multi-factor authentication not working in Plaid Link.

Solutions:

  • Ensure the customer has access to their MFA device/email
  • Try alternative MFA methods if offered by the bank
  • In test mode, use Plaid's test MFA codes (1234)
  • Contact the bank if MFA consistently fails

Best Practices


Follow these best practices for optimal Plaid integration.

User Experience

  • Show Plaid prominently for bank payment options
  • Provide fallback to manual entry if Plaid is unavailable
  • Explain the process before opening Plaid Link

Integration

  • Check availability before showing Plaid button
  • Handle errors gracefully with clear user messaging

Next Steps

Enhance bank payment processing with additional features and integrations


Manage Bank Payments

Learn about Bank Account Payment Methods to understand bank account processing, explore Payment Processing for bank payment lifecycle and settlement details, and review Payment Method Verification for verification methods and requirements.

Add More Payment Options

Accept Apple Pay payments from Apple Pay users, enable Google Pay for Google Pay users, and build custom forms with Payment Form for card and manual bank account payments.

Setup Recurring Billing

Configure Recurring Payments to setup automatic billing with saved bank accounts, enable Autopay Overview for automatic invoice payment, and manage Subscription Billing for subscription billing cycles.

Monitor and Analyze Payments

Use Webhook Events to monitor payment and connection events in real-time, check Transaction Status for bank payment status updates, and track payment metrics with Reporting Overview.


Related articles