DEV Community

Payout Rail
Payout Rail

Posted on

ACH Return Codes Explained: R01 to R85 and How to Handle Them

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
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)