Skip to content
Sign in
An image of the Stripe logo
/
Create account
Sign in
Home
Payments
Finance automation
Banking as a service
Developer tools
No-code
All products
Home
Payments
Finance automation
Home
Payments
Finance automation
Banking as a service
Developer tools
Overview
Get started
About Stripe payments
Start an integration
Payment Links
Checkout
Web Elements
Mobile Elements
Payment scenarios
During the payment
After the payment
Add payment methods
    Overview
    Payment method integration options
    Multiple configurations on dynamic payment methods
    A/B testing on dynamic payment methods
    Payment method targeting on dynamic payment methods
    Register payment method domains
    Bank debits
      ACH Direct Debit
        Accept a payment
        Save bank details
        Migrating to new APIs
        Blocked bank accounts
      Bacs Direct Debit
      Pre-authorized debit in Canada
      BECS Direct Debit in Australia
      SEPA Direct Debit
    Bank redirects
    Bank transfers
    Buy now, pay later
    Credit transfers (Sources)
    Real-time payments
    Regional card installments
    Vouchers
    Wallets
More payment scenarios
Faster checkout with Link
Other Stripe products
Connect
Terminal
Radar
Financial Connections
Crypto
Identity
Climate
Resources
About the APIs
Implementation guides
Regulation support
Testing
HomePaymentsBank debitsACH Direct Debit

Save details for future payments with ACH Direct Debit

Learn how to save payment method details for future ACH Direct Debit payments.

You can use the Setup Intents API to collect payment method details in advance, with the final amount or payment date determined later. This is useful for:

  • Saving payment methods to a wallet to streamline future purchases
  • Collecting surcharges after fulfilling a service
  • Starting a free trial for a subscription

Note

ACH Direct Debit is a delayed notification payment method, which means that funds aren’t immediately available after payment. A payment typically takes 4 business days to arrive in your account.

Set up Stripe
Server-side

First, you need a Stripe account. Register now.

With Stripe, you can instantly verify a customer’s bank account. If you want to retrieve additional data on an account, sign up for data access with Stripe Financial Connections.

Stripe Financial Connections lets your customers securely share their financial data by linking their financial accounts to your business. Use Financial Connections to access customer-permissioned financial data such as tokenized account and routing numbers, balance data, ownership details, and transaction data.

Access to this data helps you perform actions like check balances before initiating a payment to reduce the chance of a failed payment because of insufficient funds.

Use our official libraries for access to the Stripe API from your application:

Command Line
# Available as a gem sudo gem install stripe
Gemfile
# If you use bundler, you can add this line to your Gemfile gem 'stripe'

Create or retrieve a Customer
Server-side

To reuse a bank account for future payments, you must attach it to a Customer.

Create a Customer object when your customer creates an account with your business. Associating the ID of the Customer object with your own internal representation of a customer enables you to retrieve and use the stored payment method details later. If your customer hasn’t created an account, you can still create a Customer object now and associate it with your internal representation of the customer’s account later.

Create a new Customer or retrieve an existing Customer to associate with these payment details. Include the following code on your server to create a new Customer.

Command Line
curl -X POST https://api.stripe.com/v1/customers \ -u "
sk_test_4eC39HqLyjWDarjtT1zdp7dc
:"

Create a SetupIntent
Server-side

A SetupIntent is an object that represents your intent to set up a customer’s payment method for future payments. The SetupIntent tracks the steps of this set-up process.

Create a SetupIntent on your server with payment_method_types set to us_bank_account and specify the Customer’s id.

Set the permissions parameter to payment_method. If you signed up for data access with Stripe Financial Connections, specify the additional account data you require. We recommend specifying balances in the permissions parameter to access balance data on an account. Checking an account balance before confirming a payment helps prevent payment failures because of insufficient funds.

Optionally, specify which data you require immediately upon account creation with the prefetch parameter. The prefetch parameter saves you from making another API call to retrieve data after account creation and reduces the latency of data retrieval. To protect the privacy of your customer’s data, you only receive access to account data you request. Your customer can see the data you want to access before verifying their account.

Note

To expand access to additional data after a customer authenticates their account, they must re-link their account with expanded permissions.

Command Line
curl https://api.stripe.com/v1/setup_intents \ -u "
sk_test_4eC39HqLyjWDarjtT1zdp7dc
:"
\ -d customer={{CUSTOMER_ID}} \ -d "payment_method_types[]"=us_bank_account \ -d "payment_method_options[us_bank_account][financial_connections][permissions][]"=payment_method \ -d "payment_method_options[us_bank_account][financial_connections][permissions][]"=balances

Retrieve the client secret

The SetupIntent includes a client secret that the client side uses to securely complete the payment process. You can use different approaches to pass the client secret to the client side.

Retrieve the client secret from an endpoint on your server, using the browser’s fetch function. This approach is best if your client side is a single-page application, particularly one built with a modern frontend framework like React. Create the server endpoint that serves the client secret:

main.rb
get '/secret' do intent = # ... Create or retrieve the SetupIntent {client_secret: intent.client_secret}.to_json end

And then fetch the client secret with JavaScript on the client side:

(async () => { const response = await fetch('/secret'); const {client_secret: clientSecret} = await response.json(); // Render the form using the clientSecret })();

Collect payment method details
Client-side

When a customer clicks to pay with ACH Direct Debit, we recommend you use Stripe.js to submit the payment to Stripe. Stripe.js is our foundational JavaScript library for building payment flows. It will automatically handle integration complexities, and enables you to easily extend your integration to other payment methods in the future.

Include the Stripe.js script on your checkout page by adding it to the head of your HTML file.

checkout.html
<head> <title>Checkout</title> <script src="https://js.stripe.com/v3/"></script> </head>

Create an instance of Stripe.js with the following JavaScript on your checkout page.

client.js
// Set your publishable key. Remember to change this to your live publishable key in production! // See your keys here: https://dashboard.stripe.com/apikeys const stripe = Stripe(
'pk_test_TYooMQauvdEDq54NiTphI7jx'
);

Rather than sending the entire SetupIntent object to the client, use its client secret from the previous step. This is different from your API keys that authenticate Stripe API requests.

Handle the client secret carefully because it can complete the charge. Don’t log it, embed it in URLs, or expose it to anyone but the customer.

Use stripe.collectBankAccountForSetup to collect bank account details, create a PaymentMethod, and attach that PaymentMethod to the SetupIntent. Including the account holder’s name in the billing_details parameter is required to create an ACH Direct Debit PaymentMethod.

script.js
// Use the form that already exists on the web page. const paymentMethodForm = document.getElementById('payment-method-form'); const confirmationForm = document.getElementById('confirmation-form'); paymentMethodForm.addEventListener('submit', (ev) => { ev.preventDefault(); const accountHolderNameField = document.getElementById('account-holder-name-field'); const emailField = document.getElementById('email-field'); // Calling this method will open the instant verification dialog. stripe.collectBankAccountForSetup({ clientSecret: clientSecret, params: { payment_method_type: 'us_bank_account', payment_method_data: { billing_details: { name: accountHolderNameField.value, email: emailField.value, }, }, }, expand: ['payment_method'], }) .then(({setupIntent, error}) => { if (error) { console.error(error.message); // PaymentMethod collection failed for some reason. } else if (setupIntent.status === 'requires_payment_method') { // Customer canceled the hosted verification modal. Present them with other // payment method type options. } else if (setupIntent.status === 'requires_confirmation') { // We collected an account - possibly instantly verified, but possibly // manually-entered. Display payment method details and mandate text // to the customer and confirm the intent once they accept // the mandate. confirmationForm.show(); } }); });

The Stripe Financial Connections authentication flow automatically handles bank account details collection and verification. When your customer completes the authentication flow, the PaymentMethod automatically attaches to the SetupIntent, and a Financial Connections account is created.

Warning

The Stripe Financial Connections Terms describe the terms that apply to your use of Stripe Financial Connections. You may not access or use Stripe Financial Connections unless you abide by all of the terms, and by accessing or using Stripe Financial Connections, you agree to abide by all of the terms.

To provide the best user experience on all devices, set the viewport minimum-scale for your page to 1 using the viewport meta tag.

<meta name="viewport" content="width=device-width, minimum-scale=1" />

OptionalAccess data on a Financial Connections bank account
Server-side

Collect mandate acknowledgement and submit
Client-side

Before you can complete the SetupIntent and save the payment method details for the customer, you must obtain authorization for payment by displaying mandate terms for the customer to accept.

To be compliant with Nacha rules, you must obtain authorization from your customer before you can initiate payment by displaying mandate terms for them to accept. For more information on mandates, see Mandates.

When the customer accepts the mandate terms, you must confirm the SetupIntent. Use stripe.confirmUsBankAccountSetup to complete the payment when the customer submits the form.

script.js
confirmationForm.addEventListener('submit', (ev) => { ev.preventDefault(); stripe.confirmUsBankAccountSetup(clientSecret) .then(({setupIntent, error}) => { if (error) { console.error(error.message); // The payment failed for some reason. } else if (setupIntent.status === "requires_payment_method") { // Confirmation failed. Attempt again with a different payment method. } else if (setupIntent.status === "succeeded") { // Confirmation succeeded! The account is now saved. // Display a message to customer. } else if (setupIntent.next_action?.type === "verify_with_microdeposits") { // The account needs to be verified via microdeposits. // Display a message to consumer with next steps (consumer waits for // microdeposits, then enters a statement descriptor code on a page sent to them via email). } }); });

Note

stripe.confirmUsBankAccountSetup may take several seconds to complete. During that time, disable resubmittals of your form and show a waiting indicator (for example, a spinner). If you receive an error, show it to the customer, re-enable the form, and hide the waiting indicator.

If successful, Stripe returns a SetupIntent object, with one of the following possible statuses:

StatusDescriptionNext Steps
succeededThe bank account has been instantly verified or verification was not necessary.No action needed
requires_actionFurther action needed to complete bank account verification.Step 6: Verifying bank accounts with microdeposits

After successfully confirming the SetupIntent, an email confirmation of the mandate and collected bank account details must be sent to your customer. Stripe handles these by default, but you can choose to send custom notifications if you prefer.

Verify bank account with microdeposits
Client-side

Not all customers can verify the bank account instantly. This step only applies if your customer has elected to opt out of the instant verification flow in the previous step.

In these cases, Stripe sends a descriptor_code microdeposit and might fall back to an amount microdeposit if any further issues arise with verifying the bank account. These deposits take 1-2 business days to appear on the customer’s online statement.

  • Descriptor code. Stripe sends a single, 0.01 USD microdeposit to the customer’s bank account with a unique, 6-digit descriptor_code that starts with SM. Your customer uses this string to verify their bank account.
  • Amount. Stripe sends two, non-unique microdeposits to the customer’s bank account, with a statement descriptor that reads ACCTVERIFY. Your customer uses the deposit amounts to verify their bank account.

The result of the stripe.confirmUsBankAccountSetup method call in the previous step is a SetupIntent in the requires_action state. The SetupIntent contains a next_action field that contains some useful information for completing the verification.

next_action: { type: "verify_with_microdeposits", verify_with_microdeposits: { arrival_date: 1647586800, hosted_verification_url: "https://payments.stripe.com/…", microdeposit_type: "descriptor_code" } }

If you supplied a billing email, Stripe notifies your customer via this email when the deposits are expected to arrive. The email includes a link to a Stripe-hosted verification page where they can confirm the amounts of the deposits and complete verification.

Warning

Verification attempts have a limit of ten failures for descriptor-based microdeposits and three for amount-based ones. If you exceed this limit, we can no longer verify the bank account. In addition, microdeposit verifications have a timeout of 10 days. If you can’t verify microdeposits in that time, the SetupIntent reverts to requiring new payment method details. Clear messaging about what these microdeposits are and how you use them can help your customers avoid verification issues.

Optional: Send custom email notifications

Optionally, you can send custom email notifications to your customer. After you set up custom emails, you need to specify how the customer responds to the verification email. To do so, choose one of the following:

  • Use the Stripe-hosted verification page. To do this, use the verify_with_microdeposits[hosted_verification_url] URL in the next_action object to direct your customer to complete the verification process.

  • If you prefer not to use the Stripe-hosted verification page, create a form on your site. Your customers then use this form to relay microdeposit amounts to you and verify the bank account using Stripe.js.

    • At minimum, set up the form to handle the descriptor code parameter, which is a 6-digit string for verification purposes.
    • Stripe also recommends that you set your form to handle the amounts parameter, as some banks your customers use may require it.

    Integrations only pass in the descriptor_code or amounts. To determine which one your integration uses, check the value for verify_with_microdeposits[microdeposit_type] in the next_action object.

stripe.verifyMicrodepositsForSetup(clientSecret, { // Provide either a descriptor_code OR amounts, not both descriptor_code: `SMT86W`, amounts: [32, 45], });

When the bank account is successfully verified, Stripe returns the SetupIntent object with a status of succeeded, and sends a setup_intent.succeeded webhook event.

Verification can fail for several reasons. The failure may happen synchronously as a direct error response, or asynchronously through a setup_intent.setup_failed webhook event (shown in the following examples).

{ "error": { "code": "payment_method_microdeposit_verification_amounts_mismatch", "message": "The amounts provided do not match the amounts that were sent to the bank account. You have {attempts_remaining} verification attempts remaining.", "type": "invalid_request_error" } }
Error CodeSynchronous or AsynchronousMessageStatus change
payment_method_microdeposit_failedSynchronously, or asynchronously through webhook eventMicrodeposits failed. Please check the account, institution and transit numbers providedstatus is requires_payment_method, and last_setup_error is set.
payment_method_microdeposit_verification_amounts_mismatchSynchronouslyThe amounts provided do not match the amounts that were sent to the bank account. You have {attempts_remaining} verification attempts remaining.Unchanged
payment_method_microdeposit_verification_attempts_exceededSynchronously, or asynchronously through webhook eventExceeded number of allowed verification attemptsstatus is requires_payment_method, and last_setup_error is set.
payment_method_microdeposit_verification_timeoutAsynchronously through webhook eventMicrodeposit timeout. Customer hasn’t verified their bank account within the required 10 day period.status is requires_payment_method, and last_setup_error is set.

Test your integration

To test scenarios with instant verifications, mimic different scenarios for ACH charges by selecting the test bank account for the use case you want to test.

Send transaction emails in test mode

After you collect the bank account details and accept a mandate, send the mandate confirmation and microdeposit verification emails in test mode. To do this, provide an email in the payment_method_data.billing_details[email] field in the form of {any-prefix}+test_email@{any_domain} when you collect the payment method details.

Common mistake

You need to activate your Stripe account before you can trigger these emails in Test mode.

Test account numbers

Stripe provides several test account numbers and corresponding tokens you can use to make sure your integration for manually-entered bank accounts is ready for production.

Account numberTokenRouting numberBehavior
000123456789pm_usBankAccount_success110000000The payment succeeds.
000111111113pm_usBankAccount_accountClosed110000000The payment fails because the account is closed.
000111111116pm_usBankAccount_noAccount110000000The payment fails because no account is found.
000222222227pm_usBankAccount_insufficientFunds110000000The payment fails due to insufficient funds.
000333333335pm_usBankAccount_debitNotAuthorized110000000The payment fails because debits aren’t authorized.
000444444440pm_usBankAccount_invalidCurrency110000000The payment fails due to invalid currency.
000666666661pm_usBankAccount_failMicrodeposits110000000The payment fails to send microdeposits.
000555555559pm_usBankAccount_dispute110000000The payment triggers a dispute.

Before test transactions can complete, you need to verify all test accounts that automatically succeed or fail the payment. To do so, use the test microdeposit amounts or descriptor codes below.

Test microdeposit amounts and descriptor codes

To mimic different scenarios, use these microdeposit amounts or 0.01 descriptor code values.

Microdeposit values0.01 descriptor code valuesScenario
32 and 45SM11AASimulates verifying the account.
10 and 11SM33CCSimulates exceeding the number of allowed verification attempts.
40 and 41SM44DDSimulates a microdeposit timeout.

Accepting future payments
Server-side

When the SetupIntent succeeds, it will create a new PaymentMethod attached to a Customer. These can be used to initiate future payments without having to prompt the customer for their bank account a second time.

Command Line
curl https://api.stripe.com/v1/payment_intents \ -u
sk_test_4eC39HqLyjWDarjtT1zdp7dc
:
\ -d "amount"=1099 \ -d "currency"="usd" \ -d "customer"="{{CUSTOMER_ID}}" \ -d "payment_method"="{{PAYMENT_METHOD_ID}}" \ -d "payment_method_types[]"="us_bank_account" \ -d "confirm"="true"

OptionalInstant only verification
Server-side

OptionalMicrodeposit only verification
Server-side

OptionalUpdating the default payment method
Server-side

Was this page helpful?
Need help? Contact Support.
Watch our developer tutorials.
Check out our product changelog.
Questions? Contact Sales.
Powered by Markdoc
You can unsubscribe at any time. Read our privacy policy.
On this page
Set up Stripe
Create or retrieve a Customer
Create a SetupIntent
Collect payment method details
Collect mandate acknowledgement and submit
Verify bank account with microdeposits
Test your integration
Accepting future payments
Products Used
Payments
Elements
Checkout
Stripe Shell
Test mode
Welcome to the Stripe Shell! Stripe Shell is a browser-based shell with the Stripe CLI pre-installed. Log in to your Stripe account and press Control + Backtick (`) on your keyboard to start managing your Stripe resources in test mode. - View supported Stripe commands: - Find webhook events: - Listen for webhook events: - Call Stripe APIs: stripe [api resource] [operation] (e.g., )
The Stripe Shell is best experienced on desktop.
$