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 you're building a payout system, ACH returns are inevitable. A user's bank rejects the transfer, your API gets a return code, and you need to decide: retry, notify the user, or switch rails entirely. But what does each code actually mean, and when should it fire?
This guide walks through the most common ACH return codes (defined by Nacha, the clearing house rules body) and shows you how to decode them in production code.
The ACH Return Code Structure
Every ACH return follows the same pattern:
- R prefix (indicates a return)
- Two-digit number (R01 through R85)
- Reason description (e.g., "Insufficient Funds")
The code tells you why the bank rejected the entry, which determines whether you should retry, escalate to the user, or fail permanently.
Common Return Codes and What They Mean
R01: Insufficient Funds
When it fires: The account doesn't have enough balance to cover the debit.
What to do: This is temporary. Retry after a few days, or prompt the user to deposit funds. Don't retry immediately—the account won't suddenly be funded.
R03: No Account / Account Closed
When it fires: The account number doesn't exist, or the account has been closed.
What to do: Permanent failure. Ask the user to verify their routing and account number. Don't retry.
R04: Invalid Account Number Structure
When it fires: The account number format is invalid (e.g., too short, non-numeric).
What to do: Validation error. This should rarely reach ACH—catch it client-side. If it does, ask the user to re-enter.
R10: Unauthorized / Customer Advises Not Authorized
When it fires: The account holder disputes the transaction (fraud claim, duplicate, or genuinely unauthorized).
What to do: Permanent failure. Contact the user immediately. This can escalate to a chargeback if not resolved.
R20: Invalid Company Identification
When it fires: Your originating company ID (SEC code) is wrong or unregistered.
What to do: This is your problem, not the user's. Check your Nacha registration and retry with the correct ID.
R29: Corporate Customer Advises Not Authorized
When it fires: A business account holder says the debit wasn't authorized.
What to do: Same as R10—escalate to your compliance team. This is a dispute.
R33: Routing Number Check Digit Error
When it fires: The routing number's check digit doesn't match.
What to do: Validation error. Verify the routing number and re-submit.
Handling Returns Programmatically
Here's a pattern for decoding returns and deciding next steps:
const handleAchReturn = (returnCode, metadata) => {
const permanentCodes = ['R03', 'R04', 'R10', 'R29'];
const retryableCodes = ['R01', 'R08'];
const validationCodes = ['R20', 'R33'];
if (permanentCodes.includes(returnCode)) {
// Mark payout as failed; notify user
return {
status: 'failed',
action: 'contact_user',
message: `Bank rejected transfer (${returnCode}). Please verify account details.`
};
}
if (retryableCodes.includes(returnCode)) {
// Schedule retry in 3–5 business days
return {
status: 'pending_retry',
action: 'schedule_retry',
retryAfter: addBusinessDays(new Date(), 3)
};
}
if (validationCodes.includes(returnCode)) {
// Your error; fix and re-submit
return {
status: 'pending_resubmit',
action: 'contact_support',
message: `ACH validation error (${returnCode}). Engineering team alerted.`
};
}
// Unknown code—escalate
return {
status: 'unknown',
action: 'escalate',
message: `Unexpected return code: ${returnCode}`
};
};
Timing Matters
ACH returns arrive 1–5 business days after the original entry is submitted. Your reconciliation logic must account for this delay:
- Day 0: You submit the ACH batch.
- Day 1–2: The debit posts to the user's account (tentatively).
- Day 1–5: The user's bank can return the entry.
Always query your ACH provider's return status endpoint before marking a payout as settled.
Key Takeaway
Not all returns are created equal. R01 (insufficient funds) is temporary;
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)