ACH Return Codes Explained: R01 to R85 and How to Handle Each One
ACH Return Codes Explained: R01 to R85 and How to Handle Each One
When an ACH transaction fails, your payout doesn't just disappear—it returns with a reason code. The National Automated Clearing House Association (NACHA) defines 85 standardized return codes (R01–R85) that tell you exactly why a debit or credit entry was rejected. Understanding these codes is critical for building reliable payment systems.
Unlike credit card declines, which often come back as a single "declined" message, ACH returns are granular. Each code maps to a specific failure reason, and your handling logic should differ based on the code. Let's walk through the most common ones and how to respond.
The Big Three: R01, R03, R10
R01 – Insufficient Funds
The account exists, routing is correct, but the account holder doesn't have enough money to cover the debit.
- When it fires: 1–5 business days after the ACH entry reaches the bank.
- What to do: This is temporary. Retry after 3–5 business days, or notify the user to add funds. Don't immediately fall back to a credit card—many users will fund their account and retry.
- Recovery rate: ~40–60% on retry.
R03 – No Account / Unable to Locate Account
The routing number exists, but the account number doesn't match any account at that bank.
- When it fires: Same day or next business day.
- What to do: Don't retry. This is permanent. Ask the user to verify their account details. If they provide a corrected number, treat it as a new transaction.
- Recovery rate: ~5% (only if user corrects the account number).
R10 – Customer Advises Not Authorized
The account holder claims they didn't authorize the debit.
- When it fires: 1–60 days after the entry posts (can come back very late).
- What to do: Investigate immediately. Check your payment authorization logs. If the user did authorize it, respond to the bank within the dispute window (typically 10 business days). If they didn't, refund and flag for fraud review.
- Recovery rate: Depends on your authorization proof; if you have signed consent, ~70%.
Common Operational Codes
R02 – Account Closed
The account was closed before the ACH entry arrived.
- Retry? No. Permanent.
- Action: Route to alternate payment method or ask for updated banking details.
R04 – Invalid Routing Number
The routing number doesn't exist or is formatted incorrectly.
- Retry? No.
- Action: Validate routing numbers against the FedACH directory before submission.
R05 – Unauthorized Use of Routing Number
The originating company (ODFI) is not authorized to use this routing number.
- Retry? No. This is a configuration error on your end.
- Action: Contact your ACH processor immediately.
R07 – Authorization Revoked
The account holder revoked authorization for recurring entries.
- Retry? No for that specific authorization.
- Action: If this is a subscription or recurring payment, ask the user to re-authorize or switch to an alternate method.
R29 – Corporate Account Closed
Similar to R02, but for business accounts.
- Retry? No.
- Action: Request updated business banking details.
Less Common but Important Codes
| Code | Meaning | Retry? | Action |
|---|---|---|---|
| R06 | Returned per ODFI request | No | Investigate with processor |
| R08 | Payment stopped | No | Permanent block by account holder |
| R11 | Duplicate entry | No | Check your batch logic; don't resubmit |
| R14 | Representative payee deceased | No | Escalate; may be probate issue |
| R16 | Account frozen | No | Contact bank; may be temporary |
| R20 | Non-transaction account | No | Account type doesn't support ACH |
| R23 | Entry amount exceeds limit | Maybe | Reduce amount and retry, or split entry |
Building Retry Logic
javascript
function handleAchReturn(returnCode, transactionId) {
const retryable = ['R01', 'R23'];
const permanent = ['R03', 'R04', 'R05', 'R29'];
const escalate = ['R10', 'R08', 'R16'];
if (retryable.includes(returnCode)) {
scheduleRetry(transactionId, 3); // retry in 3 days
} else if (permanent.includes(returnCode)) {
markFailed(transactionId);
notifyUser('Please update your banking details');
} else if (escalate.includes(returnCode)) {
flagForReview(transactionId, returnCode
---
*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)