Migrating to new ACH Direct Debit APIs
Learn why and how to migrate to new APIs.
Stripe is removing support for ACH Direct Debit using legacy integrations.
If you create legacy ACH Direct Debit payments, you must migrate to the Payment Intents API or Checkout Sessions API.
Feature comparison
Stripe’s new APIs offer features that aren’t available in legacy integrations:
| Feature | Legacy Integrations | Payment Intents API or Checkout Sessions API |
|---|---|---|
| Checkout support | No | Yes |
| Payment Element support | No | Yes |
| Dynamic payment method support | No | Yes |
| Settlement speed | T+6 | T+4 (T+2 when using faster settlement) |
| Instant bank account verification | Only available through custom, third-party integrations | Instant verification with Financial Connections |
| Fraud prevention | No | Radar for ACH Balance checks using Financial Connections Smart Retries |
| Supported countries | US | US, EU and UK |
Compare the Checkout Sessions and Payment Intents APIs
Stripe offers two new APIs to accept ACH Direct Debits payments: Payment Intents and Checkout Sessions APIs.
- Checkout Sessions API: Supports common checkout workflows with built-in features that remove the need for custom code and is recommended for most developers.
- Payment Intents API: A lower-level API for building your own checkout flow. It requires significantly more code and ongoing maintenance. We recommend Checkout Sessions for most integrations.
Learn more about the differences, and how to evaluate which is right for you.
Build an ACH Direct Debit integration
To build an ACH Direct Debit integration on Payment Intents or Checkout Sessions:
- Enable ACH Direct Debit in your Payment methods settings.
- To collect and use new payment methods, integrate with ACH on Payment Intents or Checkout Sessions.
- For bank accounts previously collected using the Tokens API, you can continue to use saved
BankAccountobjects asPaymentMethodobjects with the Payment Intents API. For details, see Migrate existing bank accounts. - Test your integration.
- Gradually migrate all payments using existing bank accounts to the Payment Intents or Checkout Sessions API.
- Remove your legacy integration.
Behavioural differences
Some features that exist in both APIs work differently. If you rely on any of the following behaviours, update your integration accordingly.
| Behaviour | Legacy Integrations | Payment Intents or Checkout Sessions API |
|---|---|---|
| Mandates | Not enforced by the API. | Enforced by the API. Payments can’t be initiated without an active mandate. Mandates can become inactive, which renders the payment method unusable. This can occur when a customer disputes a payment, when certain payment failures occur, or when Stripe becomes aware that the payment method is no longer valid. For more information, see Blocked bank accounts. If you reuse bank accounts created with the Tokens API, you must first create mandates for them. See Migrate existing bank accounts. |
| Balance transactions | Created when the charge is created. The Balance transaction object is present in the charge.pending event and the API response. | Created when the payment is submitted to the banking partner. The Balance transaction object is present in the charge.updated event, but not the API response. |
| Payouts/Transfers | Funds settle in source_type=bank_account. | Funds settle in source_type=card, shared with cards and other payment methods. If you manually specify the balance type for payouts or Connect transfers, update your integration to use the new balance type. During migration, you receive one payout for each balance type until all new payments use the new API. For separate charges and transfers with source_transaction, wait for the charge.updated webhook before creating a transfer. Payout statement descriptors may differ between source_type=bank_account and source_type=card payouts. |
| Microdeposits | Two microdeposits of random, small amounts for verification. No hosted verification UI. | One 1¢ microdeposit with a descriptor code for verification. In rare cases, Stripe sends two microdeposits of random, small amounts instead. Stripe provides a hosted verification page and sends automatic reminder emails to customers. Customers must verify their bank account within 10 days. If they don’t verify in time, the PaymentIntent or SetupIntent reverts to requiring new payment method details. |
| Emails | Stripe doesn’t send automatic emails. | Stripe automatically emails customers a mandate confirmation when mandates are created. When a customer attempts to verify a bank account using microdeposits, Stripe emails customers with a link to a hosted verification page. You can disable Stripe emails and send custom notifications instead. For sample mandate text and required content, see Mandate and microdeposit emails. |
Webhook differences
If you previously listened to Charge events, you might need to update your integration to listen to new event types. The following table shows how webhook events differ.
| Old webhook | New webhook on Checkout | New webhook on Payment Intents | Special instructions |
|---|---|---|---|
charge.pending | payment_intent.processing | payment_intent.processing | In legacy integrations, charge.pending includes balance transaction. In new integrations, the balance transaction isn’t available until the charge.updated event. |
charge.updated | charge.updated | charge.updated | Sent when the payment is submitted to the banking partner. Includes the balance transaction. |
charge.succeeded | checkout.session.completed | payment_intent.succeeded | The charge.succeeded webhook is also sent, so you don’t have to update your integration to listen to the new webhook. |
charge.failed | Not applicable. The customer can re-attempt the payment on the same Checkout Session until it expires, at which point you receive a checkout.session.expired event. | payment_intent.payment_failed | The charge.failed webhook is also sent, so you don’t have to update your integration to listen to the new webhook. |
charge.dispute.created | charge.dispute.created | charge.dispute.created | Not applicable |
| Not applicable | mandate.updated | mandate.updated | Sent when a mandate becomes inactive. |
Identify Legacy ACH Payments
On a Charge object, the payment_method_details.type property is ach_debit for the legacy integration and us_bank_account for the newer integration.
A legacy ACH payment is created when a legacy BankAccount is the payment source. This happens when:
- You call the Create Charge API .
- A Subscription or Invoice charges a customer whose default _ source is a legacy BankAccount, and no default _ payment _ method is set on the customer, subscription, or invoice.
- You call the Payment Intents API with a payment _ method _ type of ach _ debit .
