ACH Return Codes Explained: A Developer's Guide to R01–R85
ACH Return Codes Explained: A Developer's Guide to R01–R85
When an ACH transaction fails, you don't get a generic "error." You get a return code—a two-character alphanumeric that tells you exactly why the National Automated Clearing House rejected the debit or credit. As a developer building payment systems, understanding these codes is the difference between a retry that will succeed and one that will fail every time.
The Nacha Operating Rules define 85 return codes (R01 through R85). Each one maps to a specific rejection reason, and your handling logic must differ based on which code you receive.
The Big Three: Most Common Returns
R01 — Insufficient Funds
The account exists, the routing number is valid, but the account holder doesn't have enough money. This is recoverable: you should retry after a few days (typically 2–5 days), or implement dunning logic to notify the user and ask them to try again.
{
"return_code": "R01",
"reason": "Insufficient Funds",
"is_retryable": true,
"recommended_retry_delay_days": 3,
"customer_action": "Advise user to add funds and retry"
}
R03 — No Account
The routing number is valid, but the account number doesn't exist at that institution. This is not retryable. You should flag the account as invalid, halt future attempts, and ask the user to verify their account details.
R10 — Customer Advises Not Authorized
The customer contacted their bank and said "I didn't authorize this." This is a dispute, not a processing error. You should log it, notify your compliance team, and investigate the original authorization. Retrying is pointless and legally risky.
The Full Taxonomy
Nacha groups return codes by root cause:
| Code | Reason | Retryable? | Next Step |
|---|---|---|---|
| R01 | Insufficient Funds | Yes | Retry in 2–5 days |
| R02 | Account Closed | No | Update account status |
| R03 | No Account | No | Request new account details |
| R04 | Invalid Account Type | No | Verify account type with user |
| R05 | Account Frozen | Maybe | Contact user; may be temporary |
| R07 | Authorization Revoked | No | Request new authorization |
| R10 | Customer Advises Not Authorized | No | Investigate; escalate |
| R11 | Check Digit Error | No | Validate routing/account format |
| R20 | Non-Transaction Account | No | Use different account |
| R29 | Corporate Account Restricted | No | Verify account eligibility |
Codes R30–R39 cover routing and format issues; R40–R49 cover duplicate/timing problems; R50–R69 cover authorization and customer disputes; R70–R85 cover miscellaneous and administrative issues.
Building Your Return Handler
When you receive a return, your code should:
- Parse the return code from your ACH processor's webhook or API response.
- Classify it as retryable, non-retryable, or escalation-required.
- Take action based on classification.
RETURN_LOGIC = {
"R01": {"retryable": True, "delay_days": 3},
"R03": {"retryable": False, "action": "disable_account"},
"R10": {"retryable": False, "action": "escalate_to_compliance"},
"R11": {"retryable": False, "action": "request_new_details"},
}
def handle_ach_return(transaction_id, return_code):
logic = RETURN_LOGIC.get(return_code, {})
if logic.get("retryable"):
schedule_retry(transaction_id, delay_days=logic["delay_days"])
elif logic.get("action") == "disable_account":
mark_account_invalid(transaction_id)
notify_user("Your account details are invalid")
elif logic.get("action") == "escalate_to_compliance":
create_compliance_ticket(transaction_id, return_code)
log_return(transaction_id, return_code, logic)
Reconciliation Implications
Returns settle 1–2 business days after the original debit attempt. Your reconciliation logic must account for this lag: don't assume a transaction is final until you've confirmed no return within the Nacha window (typically 5 business days for most codes).
Key Takeaway
Not all ACH failures are the same. R01 means "try again later." R03 means "this account is dead." R10 means "legal issue." Build your retry and escalation logic around the actual code, not a generic retry counter. Your success rate—and your compliance posture—will improve
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)