ACH direct debit
ACH direct debit lets your US end-users pay straight from a bank account instead of a card. Fees are lower than cards, but unlike a card charge it settles asynchronously — a payment stays in progress for a few business days before it completes or fails. This guide covers enabling ACH on a payment, how bank verification works, the payment lifecycle, saving a bank account for reuse, and charging one off-session.
ACH builds on the same Payments API as cards — read Creating Payments first.
ACH direct debit works for US bank accounts and only for payments in US dollars. A payment that allows ACH_DIRECT_DEBIT in any other currency is rejected with PAYMENT_ACH_REQUIRES_USD.
Accepting ACH on a payment
Pass ACH_DIRECT_DEBIT in supportedPaymentMethods when you create the payment. Include CREDIT_CARD too to offer both:
const result = await forte.projects.createPayment({
projectId,
userId,
createPaymentRequest: {
currency: "usd",
lineItems: [{ description: "Annual plan", unitAmountCents: 120000, quantity: 1 }],
supportedPaymentMethods: ["CREDIT_CARD", "ACH_DIRECT_DEBIT"],
},
});Confirm the returned stripeClientSecret with the Stripe Payment Element exactly as you do for cards (see Confirming the payment). The Payment Element renders a "US bank account" option alongside the card form.
Bank verification
Before a bank account can be debited, the customer's ownership of it must be verified. There are two paths, and Forte prefers the faster one automatically:
- Instant verification. The customer links their bank by logging in through Stripe's hosted bank-link flow inside the Payment Element. Verification is immediate and the payment proceeds with no further action. This is the common path for major US banks.
- Microdeposit verification (fallback). If instant verification isn't available — e.g. the customer types their routing and account number manually — Stripe sends a small deposit to the account, which arrives in 1–2 business days. The customer then confirms that deposit to finish verifying.
You don't build anything for the microdeposit step. Stripe emails the customer a link to its own hosted verification page where they enter the deposit amount; once they do, the payment continues on its own. There is nothing to handle in your checkout.
Payment lifecycle
An ACH payment moves through the same payment states as a card payment, but it spends real time in PROCESSING:
| State | Meaning for ACH |
|---|---|
DRAFT | Created, not yet confirmed by the customer. Expires after 24 hours if never confirmed. |
PROCESSING | The bank is being verified and/or the debit is settling. This can last several business days. Forte does not cancel a payment while it's here — only unconfirmed DRAFT payments are swept. |
COMPLETED | The debit cleared. Fires the PAYMENT_COMPLETED trigger. |
FAILED | Verification or the debit failed (e.g. insufficient funds, account closed). Fires the PAYMENT_FAILED trigger. |
Forte advances these states automatically as Stripe reports progress. Because the customer usually isn't watching when an ACH debit finally clears or bounces, react to the PAYMENT_COMPLETED / PAYMENT_FAILED triggers rather than the synchronous createPayment response. Don't grant access on PROCESSING — wait for COMPLETED.
Saving a bank account for reuse
A bank account can be saved to a user the same way a card is — through a setup flow — so you can charge it again later without re-collecting details. In the Forte console, open the user and use Add payment method; the Payment Element offers "US bank account" and saves it once verified. Programmatically, start the setup, confirm it with the Stripe Elements client, and the verified account attaches to the user.
Saved bank accounts appear in listPaymentMethods with type: "us_bank_account", the bankName, the account last4, and accountType (checking/savings):
{
"id": "pm_...",
"type": "us_bank_account",
"bankName": "STRIPE TEST BANK",
"last4": "6789",
"accountType": "checking",
"isDefault": false
}If the account needed microdeposits, it is saved but not chargeable until the customer finishes verifying it. The Forte console shows a "verification pending" state when this happens.
Charging a saved method off-session (server-initiated)
Once a bank account is saved and verified, charge it without the customer present by passing offSession: true and its paymentMethodId — the same off-session flow as a saved card:
const result = await forte.projects.createPayment({
projectId,
userId,
createPaymentRequest: {
currency: "usd",
lineItems: [{ description: "Usage — May", unitAmountCents: 4200, quantity: 1 }],
paymentMethodId: "pm_...",
offSession: true,
},
});
// result.payment.state === "PROCESSING" — wait for the PAYMENT_COMPLETED / PAYMENT_FAILED trigger.Unlike a card, an off-session ACH charge can't decline on the spot — it returns PROCESSING and can still fail days later, which is why the PAYMENT_FAILED trigger matters. For recurring billing, use Subscriptions (note: subscriptions are card-only today).
Testing ACH in sandbox
In a sandbox project (Stripe Test mode), use Stripe's test bank account — routing number 110000000, account number 000123456789 — in the Payment Element to simulate a successful ACH payment. Stripe provides additional test account numbers that simulate failures and the microdeposit flow.