ACH Return Codes Explained: R01, R03, R10 & When to Retry vs. Reroute
When a payout fails, the reason matters. ACH return codes tell you why a transfer bounced—and whether you should retry, escalate to customer support, or switch payment rails entirely.
If you're building a fintech product, marketplace, or payroll system, understanding the Nacha return code set (R01–R85) is non-negotiable. A mishandled return can orphan funds, frustrate users, and trigger compliance headaches.
The Most Common ACH Return Codes
R01: Insufficient Funds
The account exists and is accessible, but the balance is too low. This is temporary and reversible.
- When it fires: Receiver's bank rejects the debit during posting.
-
Developer action: Flag the transaction as
INSUFFICIENT_FUNDS. Retry after 1–2 business days (the user may have deposited more). Don't immediately fail the payout. - Example: A gig worker's bank account has $50; you're trying to pay out $200.
R03: No Account / Unable to Locate Account
The account number or routing number doesn't match any account at the receiver's bank.
- When it fires: Bank validation fails during the ACH file processing cycle.
-
Developer action: Mark as
ACCOUNT_INVALID. Do not retry the same account. Prompt the user to re-enter banking details. This is a data entry error, not a temporary issue. - Example: User transposed two digits in their account number.
R10: Customer Advises Unauthorized
The account holder disputes the transaction. Often a fraud claim or authorization mismatch.
- When it fires: Receiver's bank receives a customer complaint within the ACH return window (typically 2 business days post-settlement).
- Developer action: Escalate to compliance. Don't retry. Investigate whether the user authorized the payout. If legitimate, contact the receiver's bank to dispute the return (within your bank's window). If fraudulent, block the account and file a report.
- Example: A user's spouse claims they didn't authorize a transfer.
Other High-Impact Codes
| Code | Reason | Retry? | Action |
|---|---|---|---|
| R02 | Bank account closed | No | Collect new account details |
| R04 | Invalid account number format | No | Validate format before submission |
| R05 | Unauthorized user / account holder | No | Verify authorization; escalate if needed |
| R07 | Authorization revoked | No | Contact receiver; investigate |
| R08 | Payment stopped | No | Check with originator (your customer) |
| R16 | Account frozen / legal hold | No | Escalate to compliance |
| R20 | Non-transaction account | No | Collect valid checking/savings account |
| R29 | Corporate account restriction | No | Confirm account type with receiver |
Building Retry Logic Around Returns
Here's a pseudocode pattern for handling returns programmatically:
async function handleAchReturn(returnCode, payout) {
const retryable = ['R01']; // Insufficient funds only
const reroutable = ['R01', 'R02']; // Try RTP or Visa Direct
if (retryable.includes(returnCode)) {
// Retry after 2 business days
payout.status = 'PENDING_RETRY';
payout.nextRetryAt = addBusinessDays(new Date(), 2);
await db.savePayout(payout);
await notifyUser('Payment delayed; will retry soon.');
} else if (reroutable.includes(returnCode)) {
// Attempt alternate rail (e.g., RTP for faster settlement)
const rtpResult = await initiateRtp(payout);
if (rtpResult.success) {
payout.status = 'ROUTED_TO_RTP';
await db.savePayout(payout);
} else {
payout.status = 'MANUAL_REVIEW';
await escalateToSupport(payout);
}
} else {
// Non-retryable: prompt user for action
payout.status = 'ACTION_REQUIRED';
await notifyUser(`Payment failed (${returnCode}). Please update your banking details.`);
}
}
Key Takeaways
- R01 is your only true retry candidate. Most other codes indicate user error or fraud—retrying won't help.
- Return timing matters. ACH returns arrive 2–5 business days after settlement. Plan your reconciliation loop accordingly.
- Reroute strategically. If ACH fails for a time-sensitive payout, consider RTP (Real-Time Payments) or Visa Direct—both settle in minutes, though at higher cost.
- Log everything. Store the return
Decoding ACH return codes programmatically? The ACH Return Codes API returns the full Nacha R01–R85 set with plain-language descriptions and handling guidance.
Top comments (0)