Listen for the payment_intent.payment_failed webhook event on your server; ACH returns arrive asynchronously, typically 2–4 business days after debit initiation
Inspect the PaymentIntent's last_payment_error object in the webhook payload: check error.code for Stripe error codes such as 'insufficient_funds' (maps to NACHA R01) or 'no_account' (R03)
Check last_payment_error.decline_code for additional context on why the debit was returned by the receiving bank
If the error indicates an account issue (e.g., account closed, invalid account number), update your records and stop future debits to that account; Stripe may block the bank account from future charges
For insufficient funds (R01), you may retry after confirming with the customer; NACHA rules limit retry attempts so do not retry indefinitely
Send appropriate customer notifications and, where required by NACHA rules, obtain new authorization before re-debiting
Known gotchas
Stripe translates NACHA R-codes into its own error codes; there is not a direct 1:1 mapping exposed via the API, so rely on Stripe's error.code and decline_code rather than attempting to infer raw R-codes
When an ACH debit is returned for reasons other than insufficient funds, Stripe may permanently block the bank account; attempting future charges to a blocked account will fail synchronously
ACH return windows can extend up to 60 days for unauthorized consumer debits (R10, R29); do not assume a settled payment is final until the return window closes
Give your agent this knowledge — and 200+ more routes
One MCP install gives any agent live access to the full route map, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp