ACH Return Codes Explained: R01 to R85 and How to Handle Them
ACH Return Codes Explained: R01 to R85 and How to Handle Them
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. Understanding these codes is non-negotiable if you're building a payout system that doesn't lose money or customers to silent failures.
The Nacha Operating Rules define return codes R01 through R85. Each one maps to a specific rejection reason, and each one requires a different response from your application. Let's walk through the most common ones and how to handle them programmatically.
The Big Three: R01, R03, R10
R01: Insufficient Funds
The receiver's account doesn't have enough money to cover the debit. This is the most common return in consumer payout systems.
- When it fires: During the settlement window, typically 1–2 business days after the ACH file is sent.
- Your action: Mark the payout as failed and retry within 3–5 days. Many receivers will have funds by then. After 2–3 failed attempts, escalate to manual review or suggest an alternative payment method (card, wire).
- Code example:
{
"return_code": "R01",
"reason": "Insufficient Funds",
"retry_eligible": true,
"retry_after_days": 3,
"max_retries": 3
}
R03: No Account
The account number doesn't exist, or the routing number is invalid. This is permanent.
- When it fires: During settlement.
- Your action: Do not retry. Flag the payout as permanently failed. Contact the user to verify their banking details.
- Code example:
if return_code == "R03":
payout.status = "FAILED_NO_ACCOUNT"
notify_user("Please verify your bank account details")
# Do not retry
R10: Customer Advises Not Authorized
The receiver claims they didn't authorize the transaction. This can be a dispute or a genuine error.
- When it fires: Up to 60 days after settlement (per Nacha rules).
- Your action: Investigate immediately. If legitimate, reverse the transaction and refund the receiver. If fraudulent, document it and escalate to your compliance team.
Mid-Tier Returns: R04, R05, R07, R09
R04: Invalid Account Number Structure
The account number format is invalid for that bank (e.g., too many digits).
- Action: Do not retry. Ask the user to re-enter their account number.
R05: Account Closed
The account is closed and cannot receive deposits.
- Action: Do not retry. Request updated banking information.
R07: Authorization Revoked
The receiver revoked permission for ACH debits. This is common when a user cancels a subscription.
- Action: Do not retry. Update your records and remove the ACH authorization.
R09: Uncollected Funds
The funds are pending (e.g., a recent deposit hasn't cleared). Similar to R01 but different root cause.
- Action: Retry after 2–3 days.
Building Retry Logic
Here's a practical pattern for handling returns:
RETRY_ELIGIBLE = {"R01", "R09", "R02", "R08"}
NO_RETRY = {"R03", "R04", "R05", "R07", "R13", "R16"}
DISPUTE_CODES = {"R10", "R29"}
def handle_ach_return(payout, return_code):
if return_code in RETRY_ELIGIBLE:
payout.retry_count += 1
if payout.retry_count <= 3:
payout.next_retry = now() + timedelta(days=3)
payout.status = "PENDING_RETRY"
else:
payout.status = "FAILED_MAX_RETRIES"
escalate_to_support(payout)
elif return_code in NO_RETRY:
payout.status = "FAILED_PERMANENT"
notify_user_update_banking_info(payout)
elif return_code in DISPUTE_CODES:
payout.status = "DISPUTED"
open_investigation(payout)
The Complete Picture
ACH has 85 defined return codes, but you'll encounter maybe 15 in production. The key is bucketing them into three categories: retry-eligible, permanent failures, and disputes. Each bucket gets a different workflow.
Document your mapping in code comments or a database table. Test your return handling with a sandbox environment before going live. And monitor your return rates by code—if R01 spikes, your user base may be experiencing financial hardship, which is a leading indicator
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)