Skip to content
Create account
or
Sign in
The Stripe Docs logo
/
Ask AI
Create account
Sign in
Get started
Payments
Finance automation
Platforms and marketplaces
Money management
Developer tools
Get started
Payments
Finance automation
Get started
Payments
Finance automation
Platforms and marketplaces
Money management
Overview
About Stripe payments
Upgrade your integration
Payments analytics
Online payments
OverviewFind your use caseManaged Payments
Use Payment Links
Build a checkout page
Build an advanced integration
Build an in-app integration
Payment methods
Add payment methods
    Overview
    Payment method integration options
    Manage default payment methods in the Dashboard
    Payment method types
    Cards
    Bank debits
      ACH Direct Debit
        Accept a payment
        Save bank details
        Migrating from the Charges API
        Migrating from another processor
        Blocked bank accounts
        SEC codes
      Bacs Direct Debit
      Pre-authorized debit in Canada
      Australia BECS Direct Debit
      New Zeland BECS Direct Debit
      SEPA Direct Debit
    Bank redirects
    Bank transfers
    Credit transfers (Sources)
    Buy now, pay later
    Real-time payments
    Vouchers
    Wallets
    Enable local payment methods by country
    Custom payment methods
Manage payment methods
Faster checkout with Link
Payment interfaces
Payment Links
Checkout
Web Elements
In-app Elements
Payment scenarios
Custom payment flows
Flexible acquiring
Orchestration
In-person payments
Terminal
Other Stripe products
Financial Connections
Crypto
Climate
HomePaymentsAdd payment methodsBank debitsACH Direct Debit

Accept an ACH Direct Debit payment

Build a custom payment form or use Stripe Checkout to accept payments with ACH Direct Debit.

Copy page

Accepting ACH Direct Debit payments on your website consists of:

  • Creating an object to track a payment
  • Collecting payment method information with instant verifications enabled by Stripe Financial Connections
  • Submitting the payment to Stripe for processing
  • Verifying your customer’s bank account

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.

Stripe uses the payment object, the Payment Intent, to track and handle all the states of the payment until the payment completes.

Set up Stripe
Server-side

First, you need a Stripe account. Register now.

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

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

Create or retrieve a customer
Recommended
Server-side

Create a Customer object when your user creates an account with your business, or retrieve an existing Customer associated with this user. 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. Include an email address on the Customer to enable Financial Connections’ return user optimization.

Command Line
cURL
curl https://api.stripe.com/v1/customers \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d email={{CUSTOMER_EMAIL}}

Create a PaymentIntent
Server-side

A PaymentIntent is an object that represents your intent to collect payment from a customer and tracks the lifecycle of the payment process through each stage.

Create a PaymentIntent on your server and specify the amount to collect and the usd currency. If you already have an integration using the Payment Intents API, add us_bank_account to the list of payment method types for your PaymentIntent. Specify the id of the Customer.

If you want to reuse the payment method in the future, provide the setup_future_usage parameter with the value of off_session.

For more information on Financial Connections fees, see pricing details.

By default, collecting bank account payment information uses Financial Connections to instantly verify your customer’s account, with a fallback option of manual account number entry and microdeposit verification. See the Financial Connections docs to learn how to configure Financial Connections and access additional account data to optimize your ACH integration. For example, you can use Financial Connections to check an account’s balance before initiating the ACH payment.

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
curl https://api.stripe.com/v1/payment_intents \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d amount=1099 \ -d currency=usd \ -d setup_future_usage=off_session \ -d customer={{CUSTOMER_ID}} \ -d "payment_method_types[]"=us_bank_account

Retrieve the client secret

The PaymentIntent 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
Ruby
get '/secret' do intent = # ... Create or retrieve the PaymentIntent {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 PaymentIntent 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.collectBankAccountForPayment to collect bank account details with Financial Connections, create a PaymentMethod, and attach that PaymentMethod to the PaymentIntent. 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.collectBankAccountForPayment({ clientSecret: clientSecret, params: { payment_method_type: 'us_bank_account', payment_method_data: { billing_details: { name: accountHolderNameField.value, email: emailField.value, }, }, }, expand: ['payment_method'], }) .then(({paymentIntent, error}) => { if (error) { console.error(error.message); // PaymentMethod collection failed for some reason. } else if (paymentIntent.status === 'requires_payment_method') { // Customer canceled the hosted verification modal. Present them with other // payment method type options. } else if (paymentIntent.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 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 PaymentIntent, and creates a Financial Connections Account.

Common mistake

Bank accounts that your customers link through manual entry and microdeposits won’t have access to additional bank account data like balances, ownership, and transactions.

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 the payment
Client-side

Before you can initiate the payment, you must obtain authorization from your customer by displaying mandate terms for them 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 PaymentIntent. Use stripe.confirmUsBankAccountPayment to complete the payment when the customer submits the form.

script.js
confirmationForm.addEventListener('submit', (ev) => { ev.preventDefault(); stripe.confirmUsBankAccountPayment(clientSecret) .then(({paymentIntent, error}) => { if (error) { console.error(error.message); // The payment failed for some reason. } else if (paymentIntent.status === "requires_payment_method") { // Confirmation failed. Attempt again with a different payment method. } else if (paymentIntent.status === "processing") { // Confirmation succeeded! The account will be debited. // Display a message to customer. } else if (paymentIntent.next_action?.type === "verify_with_microdeposits") { // The account needs to be verified through 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 through email). } }); });

Note

stripe.confirmUsBankAccountPayment 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 PaymentIntent object, with one of the following possible statuses:

StatusDescriptionNext Steps
requires_actionFurther action is needed to complete bank account verification.Step 6: Verifying bank accounts with microdeposits
processingThe bank account was instantly verified or verification isn’t necessary.Step 7: Confirm the PaymentIntent succeeded

After successfully confirming the PaymentIntent, 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.confirmUsBankAccountPayment method call in the previous step is a PaymentIntent in the requires_action state. The PaymentIntent 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 through 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 PaymentIntent 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.verifyMicrodepositsForPayment(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 PaymentIntent object with a status of processing, and sends a payment_intent.processing webhook event.

Verification can fail for several reasons. The failure may happen synchronously as a direct error response, or asynchronously through a payment_intent.payment_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_payment_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_payment_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_payment_error is set.

Confirm the PaymentIntent succeeded
Server-side

ACH Direct Debit is a delayed notification payment method. This means that it can take up to four business days to receive notification of the success or failure of a payment after you initiate a debit from your customer’s account.

The PaymentIntent you create initially has a status of processing. After the payment has succeeded, the PaymentIntent status is updated from processing to succeeded.

We recommend using webhooks to confirm the charge has succeeded and to notify the customer that the payment is complete. You can also view events on the Stripe Dashboard.

The following events are sent when the PaymentIntent status is updated:

EventDescriptionNext Step
payment_intent.processingThe customer’s payment was submitted to Stripe successfully.Wait for the initiated payment to succeed or fail.
payment_intent.succeededThe customer’s payment succeeded.Fulfill the goods or services that were purchased.
payment_intent.payment_failedThe customer’s payment was declined. This can also apply to a failed microdeposit verification.Contact the customer through email or push notification and request another payment method. If the webhook was sent due to a failed microdeposit verification, the user needs to enter in their bank account details again and a new set of microdeposits will be deposited in their account.

Test your integration

Learn how to test scenarios with instant verifications using Financial Connections.

Send transaction emails in a sandbox

After you collect the bank account details and accept a mandate, send the mandate confirmation and microdeposit verification emails in a sandbox.

If your domain is “example.com,” use an email format such as info+testing@example.com for testing non-card payments. You can replace “info” with a standard local term such as “support.” This format ensures emails are routed correctly.

Common mistake

You need to activate your Stripe account before you can trigger these emails while testing.

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.
000000000009pm_usBankAccount_processing110000000The payment stays in processing indefinitely. Useful for testing PaymentIntent cancellation.
000777777771pm_usBankAccount_weeklyLimitExceeded110000000The payment fails due to payment amount causing the account to exceed its weekly payment volume limit.

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.

Test settlement behavior

Test transactions settle instantly and are added to your available test balance. This behavior differs from livemode, where transactions can take multiple days to settle in your available balance.

OptionalGranular settlement speed
Server-side

OptionalInstant only verification
Server-side

OptionalMicrodeposit only verification
Server-side

OptionalResolve disputes
Server-side

OptionalPayment Reference

OptionalConfigure customer debit date
Server-side

See also

  • Save ACH Direct Debit pre-authorized debit details for future payments
Was this page helpful?
YesNo
Need help? Contact Support.
Join our early access program.
Check out our changelog.
Questions? Contact Sales.
LLM? Read llms.txt.
Powered by Markdoc