ACH Return Codes Explained: R01–R85 and How to Handle Them in Production
ACH Return Codes Explained: R01–R85 and How to Handle Them in Production
When an ACH transfer fails, your payout system receives a return code. Understanding what each code means—and when it arrives—is critical to building reliable payment flows. This guide maps the Nacha standard return codes to real developer scenarios and shows you how to decode them programmatically.
Why ACH Returns Matter
ACH transactions settle in 1–2 business days, but returns can arrive up to 5 business days later. Unlike credit cards, where declines happen in milliseconds, ACH returns are asynchronous. Your code must:
- Store the original transaction state
- Match the return to the original entry
- Decide: retry, escalate, or route to an alternate rail
- Update the user and reconcile accounting
The Major ACH Return Codes
The National Automated Clearing House Association (Nacha) defines 86 return codes (R01–R85, plus R99 for vendor errors). Here are the ones you'll encounter most:
| Code | Meaning | Cause | Action |
|---|---|---|---|
| R01 | Insufficient Funds | Account balance too low | Retry after 1–2 days or escalate |
| R03 | No Account / Account Closed | Account doesn't exist or is closed | Mark as permanent failure; notify user |
| R04 | Invalid Account Number Structure | Routing or account format wrong | Validate before retry; check data entry |
| R05 | Account Closed by Institution | Bank closed the account | Permanent failure; request new account |
| R07 | Authorization Revoked | User or bank revoked permission | Permanent failure; re-verify mandate |
| R10 | Customer Advises Unauthorized | User disputes the transaction | Investigate; may need manual review |
| R20 | Non-Transaction Account | Account type incompatible with ACH | Retry with different account type or rail |
| R29 | Corporate Customer Advises Not Authorized | Business account holder disputes entry | Escalate for manual investigation |
Handling R01: Insufficient Funds
R01 is the most common return. The receiver's account lacked funds at settlement time—but may have funds tomorrow.
async function handleAchReturn(returnCode, transaction) {
if (returnCode === 'R01') {
// Insufficient funds: retry once after 2 business days
const retryDate = addBusinessDays(new Date(), 2);
await updateTransaction(transaction.id, {
status: 'pending_retry',
nextRetryAt: retryDate,
returnReason: 'Insufficient funds',
attemptCount: (transaction.attemptCount || 0) + 1
});
// Notify user
await notifyUser(transaction.userId, {
subject: 'Payment pending – will retry',
message: `We'll try again on ${retryDate.toDateString()}`
});
return { action: 'retry', retryDate };
}
}
Handling R03 / R05: Account Doesn't Exist
R03 (no account) and R05 (account closed) are permanent failures. The account is gone or invalid.
async function handleAchReturn(returnCode, transaction) {
if (['R03', 'R05'].includes(returnCode)) {
// Permanent failure: mark account as invalid
await updateBankAccount(transaction.accountId, {
status: 'invalid',
disabledReason: returnCode === 'R03' ? 'No account' : 'Closed'
});
await updateTransaction(transaction.id, {
status: 'failed',
returnReason: `Account ${returnCode}`,
requiresUserAction: true
});
// Notify user to add a new account
await notifyUser(transaction.userId, {
subject: 'Payment failed – add a new account',
actionUrl: '/settings/bank-accounts/add'
});
return { action: 'fail_permanent', requiresNewAccount: true };
}
}
Handling R10: Unauthorized Claim
R10 means the user claims they didn't authorize the transaction. This requires investigation.
javascript
async function handleAchReturn(returnCode, transaction) {
if (returnCode === 'R10') {
// Unauthorized claim: escalate to manual review
await createFraudCase({
transactionId: transaction.id,
userId: transaction.userId,
amount: transaction.amount,
returnCode: 'R10',
priority: 'high'
});
await updateTransaction(transaction.id, {
status: 'under_review',
returnReason:
---
*Decoding ACH return codes programmatically? The [ACH Return Codes API](https://rapidapi.com/payoutrail-ach-return-codes/api/ach-return-codes-api?utm_source=nichestream&utm_medium=devto&utm_campaign=payoutrail-ach-returns) returns the full Nacha R01–R85 set with plain-language descriptions and handling guidance.*
Top comments (0)