Store and Charge Bank Accounts on File

Applies to: Web Payments SDK | Payments API | Bank Accounts API

Learn how to store and charge a bank account on file with the Web Payments SDK.

Link to section

Overview

The Web Payments SDK and Square APIs enable you to securely store customer bank accounts for one-time or recurring ACH charges, eliminating the need for customers to re-link their accounts on each visit.

Common use cases include gyms processing monthly membership fees, utility companies streamlining recurring billing, or any business that needs to collect regular ACH payments. By integrating the Bank Accounts API with the Web Payments SDK, you can implement these payment features efficiently while maintaining security and compliance.

This guide covers storing bank accounts and processing both one-time and recurring ACH payments. For ACH payments without storing accounts, see Take ACH Bank Transfer Payments.

Did you know?

Bank accounts cannot be deleted once created due to compliance and record-keeping requirements. Instead, use the DisableBankAccount endpoint to prevent future charges while maintaining historical records.

Link to section

Prerequisites

Before implementing bank account storage, ensure your application meets these requirements:

  • Location: ACH bank transfers via Web Payments SDK are only supported in the United States. For international bank account storage and payment processing, see International Development
  • Permissions: Your application needs:
    • BANK_ACCOUNTS_WRITE and BANK_ACCOUNTS_READ
    • CUSTOMERS_WRITE
    • PAYMENTS_WRITE and PAYMENTS_READ
  • Setup: Complete the ACH Bank Transfer Payments setup
  • Bank Support: Customer banks must be supported by Plaid (Square's banking partner)
Link to section

Store a Bank Account

This section walks through linking and storing a customer's bank account to their profile for future use.

A diagram showing the store bank account flow

Charging a buyer on their linked and stored bank account involves the generation and use of these token types:

Token Definition
BNONA token returned by Web Payments SDK for a Plaid-authorized bank account.
BACTA token returned by Square for a Plaid-authorized bank account stored with Square.
BAUTHA token returned by the Web Payments SDK for a buyer-authorized charge on a linked bank account. Use this token with the CreatePayment method.
Link to section

Step 1: Create a Customer

First, create a Square Customer object to associate with the bank account:

Create customer

Link to section

Step 2: Tokenize the Bank Account

The ach.tokenize with AchStoreOptions.intent set to 'STORE' presents the customer with Plaid's secure authentication interface. Set up an event listener to capture the BNON token after completion of the tokenize call:

Use the Web Payments SDK to initialize ACH and launch the Plaid authentication flow:

Link to section

Step 3: Store with Square

From your server, create the bank account record using the BNON token and the following parameters:

  • source_id - The BNON token sent to your server after the call to ach.tokenize.
  • customer_id - The unique ID for the customer generated during customer creation.

Note

Testing with Real Tokens Required

The source_id parameter must be a valid token generated by the Web Payments SDK's ach.tokenize() method. You cannot use placeholder values, example tokens from documentation, or manually created strings for testing.

If you attempt to use an invalid or made-up source_id, you'll receive this error:

{ "errors": [ { "code": "NOT_FOUND", "detail": "Bank nonce not found", "category": "INVALID_REQUEST_ERROR" } ] }

To test the CreateBankAccount endpoint, you must first:

  1. Implement the Web Payments SDK on your frontend
  2. Complete the Plaid bank linking flow
  3. Call ach.tokenize() to generate a valid token
  4. Use that token immediately in your CreateBankAccount request

Bank tokens are single-use and expire quickly, so you'll need to generate a fresh token for each test.

curl https://connect.squareupsandbox.com/v2/bank-accounts \ -X POST \ -H 'Authorization: Bearer {ACCESS_TOKEN}' \ -H 'Content-Type: application/json' \ -d '{ "idempotency_key": "{UNIQUE_KEY}", "source_id": "bnon:Ja85BvcwFYPiDZJV4H", "customer_id": "WC1GYWRIT7STE3GU4ZLQ3X76EF" }'
Link to section

One-Time Payments

Process a single charge to a customer's stored bank account with these steps. These steps assume a bank account has already been linked and stored to the customer's account.

Did you know?

The Payments API automatically rejects attempts to charge seller bank accounts (those without the "bact:" prefix). This prevents accidentally charging business accounts instead of customer accounts.

Link to section

Step 1: Retrieve Bank Accounts

Retrieve the customer's stored bank accounts using the ListBankAccounts endpoint. Important: You must include the customer_id as a query parameter to filter results to only that customer's accounts. Without this parameter, the endpoint returns all bank accounts associated with the seller's Square account, including the seller's own bank accounts.

List bank accounts

Link to section

Step 2: Display Options

Present the available bank accounts in your UI to let the customer select which account to charge. Display enough information for the customer to identify their account while maintaining security:

Recommended display fields:

  • holder_name - Account holder's name
  • bank_name - Financial institution name
  • account_type - CHECKING or SAVINGS
  • account_number_suffix - Last 3-4 digits (e.g., "...000")

Important: Never display full account or routing numbers. The account_number_suffix provides enough information for identification.

Example UI pattern:

bankAccounts.forEach(account => { // Display: "Citizens Bank - Checking (...000) - Lauren Noble" const displayText = `${account.bank_name} - ${account.account_type} (...${account.account_number_suffix}) - ${account.holder_name}`; // Verify account is ready for payments if (account.status === 'VERIFIED' && account.debitable) { // Add to payment method selection UI addPaymentOption(displayText, account.id); } });

Capture the selected account's id to use in the next step for payment authorization.

Link to section

Step 3: Authorize Payment

Use the ach.tokenize method to generate a BAUTH token for the one-time charge authorization with the following parameters:

  • bankAccountId - The stored bank account token (BACT) that was returned when your application stored the bank account with Square.
  • intent - The purpose of the authorization. In this case, to CHARGE the bank account.
  • amount - The full price of the purchase, including any taxes and fees.
  • currency - The currency of the bank account to authorize.
// Initialize ACH const ach = await payments.ach({ transactionId: '415111211611', }); // Authorize one-time charge try { await ach.tokenize({ intent: 'CHARGE', // Use CHARGE for one-time payments amount: '5.00', // Amount as string with dollars and cents currency: 'USD', bankAccountId: "bact:J71CYCQ789KnZnXi5HC", }); } catch (e) { console.error(e); }

Capture the BAUTH token using the same event listener pattern shown earlier.

Link to section

Step 4: Process Payment

On your application server, create the payment with the Payments API CreatePayment endpoint using the BAUTH token:

Create payment

Link to section

Fixed Recurring Payments

Set up automated recurring charges for the same amount at regular intervals.

A diagram showing the process recurring payment flow in the Web Payments SDK

Link to section

Understanding Recurring Charge Authorization

When you use intent: RECURRING_CHARGE with the Web Payments SDK, Square does not automatically charge the customer’s bank account on the specified frequency. The RECURRING_CHARGE operation only creates a reusable BAUTH token that’s authorized for recurring payments according to the schedule you defined. Your application is fully responsible for implementing the scheduling logic and calling the Payments API CreatePayment endpoint with this token whenever a payment is due.

Think of it as getting permission to charge on a schedule, not setting up automatic charges. You’ll need to build your own scheduling system (using cron jobs, scheduled tasks, or a third-party scheduling service) to trigger payments at the appropriate times. The frequency parameters you provide during tokenization are for authorization purposes only—they tell the customer what schedule they’re agreeing to, but don’t create any automatic payment processing on Square’s side.

Note

These steps assume a bank account has already been linked and stored to the customer's account.

Link to section

Step 1: Retrieve and Display Accounts

Follow the same process as one-time payments to retrieve and display the customer's bank accounts. Consider adding UI elements to capture subscription preferences like frequency and start date.

Link to section

Step 2: Authorize Recurring Charges

Configure the recurring payment schedule when generating the BAUTH token:

Link to section

Step 3: Process Recurring Charges

Use the BAUTH token to charge the account according to the authorized schedule. The payment process is identical to one-time payments, but you can reuse the BAUTH token for each scheduled charge without re-authorization.

Note

You don't need to charge immediately after obtaining the BAUTH token. Store it securely and use it when each scheduled payment is due.

Link to section

Variable Recurring Payments

For subscriptions with changing amounts (like usage-based billing), you'll need to add the AchVariableRecurringOptions parameter to the tokenize call.

Link to section

Browser client code

Your client app uses the Web Payments SDK to let a buyer authorize a bank transfer and the payment:

Link to section

Application server code

Note

The code examples in this section demonstrate a sample implementation pattern for managing recurring payments. Your actual implementation will depend on your application's architecture, database design, and business requirements. Consider these examples as a reference rather than a required approach.

Your application server code should perform two functions:

  1. Securely store an authorization for each customer's recurring bill
  2. Charge a customer on a pre-determined schedule

These examples show a typical business logic pattern which you might choose to adapt.

Link to section

Store the authorization for a customer

This example creates an endpoint that accepts a customer ID, an auth token and additional parameters needed to set up regular billing.

Link to section

Process a payment

Your application backend takes the payment amount and the bank charge authorization token and then calls CreatePayment to process the payment:

Link to section

Disable Bank Accounts

While you cannot delete stored bank accounts, you can disable them to prevent future charges:

Important

Disabling is permanent for that specific bank account record. To charge the account again, the customer must complete the full onboarding flow as if linking a new account.

curl https://connect.squareupsandbox.com/v2/bank-accounts/bact:J71CYCQ789KnZnXi5HC/disable \ -X POST \ -H 'Authorization: Bearer {ACCESS-TOKEN}' \ -H 'Content-Type: application/json'
Link to section

Monitoring Bank Account Status

Subscribe to these webhook events to track bank account lifecycle changes:

  • bank_account.created - Triggered when a customer links a new bank account
  • bank_account.verified - Triggered when account verification completes (account ready for payments)
  • bank_account.disabled - Triggered when an account is disabled

These events help you update your UI, notify customers, and handle account status changes automatically. See Webhooks API for setup instructions.

Link to section

Troubleshooting

Link to section

Payment Declined

  • Verify account status is VERIFIED before attempting charges
  • Ensure sufficient funds in customer's account
  • Check that BAUTH token hasn't expired (tokens have limited validity)
Link to section

Duplicate Bank Account Error

  • Check the fingerprint field of existing accounts before creating new ones
  • Use ListBankAccounts to find existing accounts for the customer
Link to section

Account Not Found

  • Verify you're using the correct customer_id when listing accounts
  • Ensure the bank account ID starts with "bact:" prefix
  • Check that the account hasn't been disabled
Link to section

Next Steps