ACH debit payments
ACH debit lets a US buyer pay once from a bank account. The buyer verifies the account instantly, accepts a debit authorization, and the money arrives days later. On Flint it is the ach_debit payment option on the same PaymentIntent, order, Refund, and Dispute resources you use for cards, with the same webhooks.
Two things set it apart from a card payment. The payment sits in processing for days after the buyer is done, so nothing about the buyer finishing means you have been paid. And a payment that has settled can be pulled back by the buyer's bank, which Flint reports as a dispute.
ACH debit is one shape: a one-time, on-session debit in USD with automatic capture and instant bank verification. The bank account is not saved, and there is no microdeposit fallback for a bank that cannot verify instantly. Keep card available beside it.
How an ACH payment flows#
- Your backend sends create the PaymentIntent with ach_debit and transaction_purpose to Flint
- Flint returns payment_collection with instant verification options to Your backend
- Browser sends the buyer verifies the bank, accepts the mandate, and a ConfirmationToken is created to Browser
- Browser sends ctoken_... to Your backend
- Your backend sends POST /confirm with the token to Flint
- Flint sends submits the debit to Buyer's bank
- Flint returns processing, also sent by webhook to Your backend
- Buyer's bank sends settles or rejects the debit, days later to Flint
- Flint sends payment_intent.succeeded or payment_intent.payment_failed to Your backend
Flint Checkout, Payment Links, and the hosted invoice page run this whole sequence for you. A PaymentIntent your own server creates and confirms follows it step by step, building on the collect-and-confirm flow in Server-confirmed payments. A checkout you build on an embedded checkout session follows it through the order, as described in Your own checkout.
Turn on ACH debit#
ACH debit is off by default. Three things have to be in place before a bank debit can be confirmed:
- The option is enabled for your account. Add
ach_debitto the checkout payment options in the dashboard or over the API. Checkout sessions and payment links read this setting. A standalone PaymentIntent names its options in the request, and an invoice uses its payment policy. - Your payment account can take bank debits. Include
accept_ach_debit_paymentsinrequested_capabilitieswhen you advance onboarding, or ask Flint support to add it to an existing account. Sandbox and live are separate accounts, and each one is activated on its own. - Flint has verified two settings on that account. Flint support confirms that the account settles on the standard schedule and that the debit authorization email reaches buyers, and records both for the environment. Until they are recorded, a confirmation with a bank account fails with
PAYMENT_OPTION_UNAVAILABLEand a message that asks you to contact support.
curl -X PATCH https://api.withflintpay.com/v1/settings \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"checkout": {"enabled_payment_options": ["card", "ach_debit"]}}'
GET /v1/capabilities does not report the bank debit capability. To see where an account stands, create a PaymentIntent with payment_options set to ["ach_debit"] in that environment. A PAYMENT_OPTION_UNAVAILABLE error carries the reason that blocks it and capability set to accept_ach_debit_payments. A created PaymentIntent means the option is enabled and the capability is active. The two support-recorded settings are checked when you confirm.
Eligibility#
Flint checks each payment on its own and leaves ACH out, or rejects it, when one of these rules is broken:
- The currency is USD and the amount is within the limits for the surface.
- Capture is automatic. ACH is not offered with
capture_methodset tomanual. - The payment is one-time and on-session. A ConfirmationToken that asks to save the bank account is rejected with
PAYMENT_OPTION_NOT_ALLOWED. - No line item on the order tracks inventory. A bank debit stays unresolved for days, and Flint does not hold stock that long, so an order with a tracked line item is card only.
- The surface offers it: a standalone PaymentIntent, a hosted or embedded checkout session or a payment link backed by an order, or a one-time invoice paid on its hosted page or through an invoice checkout session. Subscriptions, invoice autopay, virtual terminal, and any off-session charge do not offer ACH.
A request that asks for ACH where it is not offered fails with PAYMENT_OPTION_UNAVAILABLE, and the error's reason names the rule: currency_not_supported, manual_capture_not_supported, bounded_inventory_guarantee_not_supported, recurring_ach_not_supported, or source_delayed_settlement_not_supported.
Amount limits#
The minimum is $1.01, so that the $1.00 minimum processing fee stays below the payment. The ceiling depends on where the payment is collected:
| Surface | Maximum | Raised by Flint |
|---|---|---|
| Standalone PaymentIntent | $100,000.00 | $100,000.00 |
| Hosted checkout session | $50,000.00 | $100,000.00 |
| Invoice | $100,000.00 | $100,000.00 |
| Payment link | $25,000.00 | $50,000.00 |
A merchant-configured limit can narrow this range but not widen it. An amount outside the range fails at create with AMOUNT_BELOW_LIMIT or AMOUNT_EXCEEDS_LIMIT on amount_money.amount, and the message says which payment option and surface set the limit.
Transaction purpose#
Every bank debit records why the money is being taken: goods, services, or other. A standalone PaymentIntent declares it in transaction_purpose, and the value is frozen once the payment exists.
An order decides it from its line items. Any line item sold from a product whose product_type is physical or digital makes the purpose goods. If every line item is a service or fee, the purpose is services. An order with no physical or digital product and a line item that is neither a service nor a fee fails with ACH_TRANSACTION_PURPOSE_UNRESOLVED when the buyer reaches the payment step. Give ad hoc line items a product type, or take ach_debit off that checkout.
Hosted checkout and payment links#
Enable ach_debit in the session's payments.enabled_payment_options, or leave the field out to inherit your checkout settings:
curl -X POST https://api.withflintpay.com/v1/checkout-sessions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: checkout-ord-1kmn0aExample" \
-d '{
"order_id": "ord_1kmn0aExample",
"payments": {"enabled_payment_options": ["card", "ach_debit"]},
"customer_collection": {"require_email": true},
"redirects": {"success_redirect_url": "https://shop.example.com/orders/1042/thanks"}
}'
Flint Checkout offers the bank option only when ACH is available for that order and amount. When it is not, the option is left out and the buyer sees card. When it is, checkout collects a billing name and email, opens instant bank verification, shows the debit authorization, and submits.
After the buyer submits, the payment is processing. Checkout shows a receipt with a "Processing" stamp and a heading such as "Your $25.00 bank payment to Example Shop is processing". A note under it says bank payments take a few business days to clear, that the receipt will be emailed when the payment clears, and that the buyer can close the checkout page. Flint Checkout does not send the buyer to success_redirect_url while the payment is processing.
A payment link uses the same settings and the same lifecycle. A payment link checkout whose payment is processing has not completed the link. The hosted invoice page offers ACH according to the invoice's payment_policy.enabled_payment_options, and the collection attempt stays processing with an expected_settlement_at; Invoicing covers that attempt.
Your own checkout#
A checkout you build on an embedded checkout session offers ACH on the order, the same way it offers card. Enable ach_debit in the session's payments.enabled_payment_options, or leave the field out to inherit your checkout settings, then:
- Mount the Payment Element from the order's
payment_collection, read with the checkout headers. Instant verification and the debit authorization run inside it. - Create the ConfirmationToken with the buyer's billing name and email, as in Create the ConfirmationToken below.
- Pay with
POST /v1/orders/{order_id}/pay. The payment attempt staysprocessinguntil the bank answers, which takes days, and is not resumable meanwhile. Show the buyer that the payment is processing, and don't start another payment for the order. - Fulfill from
order.paid, never from the pay response.
ACH needs no return URL, because the buyer never leaves your page. The same steps work for an invoice checkout session launched with "surface": "embedded". Build your own checkout walks through the order flow.
Accept an ACH payment on your own page#
While the payment is processing#
- requires_payment_method moves to processing on confirm
- requires_payment_method moves to canceled on cancel
- processing moves to succeeded on the bank settles
- processing moves to requires_payment_method on the bank rejects
- succeeded on bank return: arrives as a Dispute
A bank debit stays processing until the buyer's bank answers, and Flint does not time it out. Nothing can be changed in the meantime: cancel returns PAYMENT_INTENT_NOT_CANCELABLE, and an update returns PAYMENT_INTENT_CANNOT_BE_UPDATED. An order whose payment is processing is not paid, and Flint holds the order's payment collection until the outcome is known.
status | What it means | What to do |
|---|---|---|
processing | The debit was submitted and the bank has not answered. | Keep the order unfulfilled. Wait for a webhook or re-read the payment. |
succeeded | The bank settled the debit. | Fulfill once, from the webhook or this read. |
requires_payment_method | The bank rejected the debit, or no bank account was collected yet. | Read last_payment_error and collect again with a new token. |
canceled | The payment was canceled before a debit was submitted. | Stop collecting. |
Warning: Fulfill on success, never on processing
A finished checkout and a processing status mean the same thing: the debit has been submitted. Fulfill from payment_intent.succeeded, or from order.paid for an order, and from nothing earlier.
Webhooks#
ACH adds no event types. payment_intent.processing is the one that matters more than it does for a card, because it is the last event you receive for days:
Refunds fire the usual refund.* events. Webhooks covers signature checks and durable delivery.
Failures and retries#
A debit the bank rejects before settling lands the PaymentIntent in requires_payment_method with a Flint-normalized last_payment_error.code. Bank return codes are not passed through.
| Code | Meaning |
|---|---|
insufficient_funds | The account could not cover the debit. |
bank_account_closed | The account is closed. |
bank_account_not_found | No account matched the details. |
bank_debit_not_authorized | The account holder did not authorize the debit. |
bank_account_restricted | The account cannot accept this debit. |
bank_debit_limit_exceeded | The debit exceeded the account's limit. |
payment_failed | The bank gave no reason Flint maps. |
The prior attempt is finished. Create a new ConfirmationToken and confirm again. A standalone integration decides whether to offer the bank option again. Flint does not rewrite payment_options after a failure.
Bank returns#
A payment that settled and is pulled back later is a bank return. Flint represents it with the same Dispute resource as a card chargeback:
case_typeisbank_returnandpayment_optionisach_debit.reasonisinsufficient_funds,bank_debit_not_authorized,bank_account_not_found, orother.statusisloston arrival.evidence_response_allowedandaction_requiredarefalse, andevidence_due_atisnull.
There is no evidence to submit and no outcome to influence. The returned money has already left your balance, so collecting again means a new payment from the buyer. Route these cases to accounting and buyer outreach rather than your evidence queue, and filter on case_type so they never look like work waiting on a deadline. Handling disputes has the full shape.
Refunds#
Refund an ACH payment through the Refunds API as you would a card. Full and partial refunds are both allowed, and a refund does not return the original processing fee.
A bank return reduces what remains refundable, exactly as a prior refund would. A full return leaves nothing to refund, so a later POST /v1/refunds fails with NOTHING_TO_REFUND. A refund and a return that race are serialized so the same money cannot move twice: if the return lands first, the refund is rejected with NOTHING_TO_REFUND, and a refund already in flight fails with failure_reason set to payment_disputed. Treat either as the buyer already having the money back through their bank.
Refunds are funded from your available balance, and a return withdraws from that same balance. When the balance cannot cover a refund, creation fails with REFUND_INSUFFICIENT_AVAILABLE_BALANCE. Restore the balance, then create the refund again with a new Idempotency-Key.
Processing fees#
ACH debit is priced as a percentage of the payment, bounded by a $1.00 minimum and a $700.00 maximum, with no fixed amount. The percentage depends on your plan and is listed in Processing fees. Because settlement is delayed, so is the fee: processing_fee_money is final when the payment succeeds, not when you confirm it. A payment that fails before it succeeds is never charged a processing fee.
Three events around a bank debit carry an event fee instead. It is charged to your merchant account, not deducted from the payment:
| Event | Fee |
|---|---|
| The buyer's bank account is verified at collection | $1.50 |
| The debit fails after Flint submits it to the bank | $4.00 |
| A settled payment is returned | $15.00 |
A payment that fails before Flint submits it to the bank carries no event fee.
Test in a sandbox#
Bank debits in test mode use routing number 110000000. Pick the account number for the outcome you need:
| Account number | Outcome |
|---|---|
000123456789 | Succeeds. |
000222222227 | Fails for insufficient funds. |
000111111113 | Fails because the account is closed. |
000111111116 | Fails because no account exists. |
000333333335 | Fails because the debit is not authorized. |
000555555559 | Succeeds, then creates a bank return. |
000000000009 | Stays processing. |
Sandbox bank accounts verify instantly. Microdeposit account numbers and verification codes do nothing, because microdeposit verification is not offered. Use 000000000009 to confirm that a processing payment leaves the order unpaid, offers no cancel, and that your integration fulfills only after payment_intent.succeeded or order.paid.
Errors#
Go-live checklist#
ach_debitis enabled in the checkout settings of the environment you are launching.- The live payment account has the bank debit capability active, and Flint support has recorded the settlement and authorization email settings for it. Sandbox evidence does not carry over.
- Checkout and standalone code send a billing name and email with every bank debit.
- Fulfillment waits for
order.paidorpayment_intent.succeeded, never a redirect orprocessing. - Your webhook endpoint subscribes to processing, success, failure, dispute, and refund events.
- Support and accounting know that a successful ACH payment can be returned later, and where bank returns show up.
- Turning
ach_debitoff stops new bank debits. Payments already processing still settle, fail, return, and refund through the same webhooks, so keep the endpoint live.
Related guides#
- Server-confirmed payments: the collect-and-confirm flow ACH builds on.
- Handling disputes: the bank return case.
- Refunds: refunds against a returned payment.
- Processing fees: the ACH rate on each plan and the event fees.
- Invoicing: ACH on the hosted invoice page.
- Build your own checkout: ACH in an embedded checkout session.
- Testing: the sandbox bank accounts beside the test cards.
- Webhooks: durable delivery of the events above.
