DEV Community

Payout Rail
Payout Rail

Posted on

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 an ACH transaction fails, you get a return code. Understanding what that code means—and how to respond—is the difference between a resilient payout system and one that silently loses money.

The National Automated Clearing House Association (Nacha) defines 85 return codes (R01 through R85), each mapped to a specific failure reason. As a developer integrating ACH, you need to know which ones are permanent, which are temporary, and which demand immediate action.

The Core Return Codes You'll See Most

R01: Insufficient Funds
The account has fewer dollars than the transaction amount. This is the most common return in production. It's not reversible by the originator—the receiver's bank rejected it at settlement.

What you do: Flag the payout as failed, notify the recipient, and let them retry after funding their account. Don't auto-retry immediately; the funds won't appear in seconds.

R03: No Account / Unable to Locate Account
The account number or routing number doesn't exist, or the account was closed. Permanent failure.

What you do: Mark the payout as unrecoverable. Update your recipient database to flag this account as invalid. Consider asking the user to re-verify their banking details before the next payout attempt.

R10: Customer Advises Unauthorized
The recipient claims they didn't authorize the transaction. This is a dispute, not a technical failure.

What you do: Log it, escalate to compliance, and prepare documentation. You may be liable if you can't prove authorization. Store consent records (timestamp, IP, session ID) for every payout.

R29: Corporate Customer Advises Not Authorized
Same as R10, but for business accounts. Treat it identically—escalate and document.

R07: Authorization Revoked by Customer
The recipient asked their bank to block future payments from you. Permanent.

What you do: Disable automatic payouts to that account. Require explicit re-authorization before any future attempt.

Handling Returns Programmatically

Here's a pattern for routing return codes to the right action:

def handle_ach_return(payout_id, return_code):
    # Permanent failures: no retry
    permanent = ['R03', 'R04', 'R07', 'R08', 'R16']

    # Temporary failures: retry after delay
    temporary = ['R01', 'R09', 'R13']

    # Disputes: escalate
    disputes = ['R10', 'R29']

    if return_code in permanent:
        mark_payout_failed(payout_id, 'permanent')
        notify_user_invalid_account(payout_id)
        return 'UNRECOVERABLE'

    elif return_code in temporary:
        increment_retry_count(payout_id)
        if get_retry_count(payout_id) < 3:
            schedule_retry(payout_id, delay_hours=48)
            return 'SCHEDULED_RETRY'
        else:
            mark_payout_failed(payout_id, 'exhausted_retries')
            return 'MAX_RETRIES_EXCEEDED'

    elif return_code in disputes:
        escalate_to_compliance(payout_id, return_code)
        return 'DISPUTE_ESCALATED'

    else:
        log_unknown_return(payout_id, return_code)
        return 'UNKNOWN'
Enter fullscreen mode Exit fullscreen mode

Timing: When Returns Arrive

ACH returns are not instant. A return window typically closes 5 business days after the original settlement date. This means:

  • Day 0: You initiate the payout.
  • Day 1–2: ACH batch processes and settles.
  • Day 1–5: Receiver's bank reviews the transaction. If rejected, they generate a return.
  • Day 2–6: Return arrives in your bank's ACH file.

Your reconciliation logic must account for this lag. Don't mark a payout as "successful" until the return window has closed.

Other Critical Codes

  • R02, R04, R05, R06: Account or routing issues. Permanent.
  • R09, R13: Duplicate or revocation request. May retry, but check with the recipient first.
  • R20–R23: Invalid account holder name or routing number. Permanent.
  • R30–R35: Payment stopped by receiver or originator. Check your records.

Key Takeaway

Map every return code to a business rule. Don't retry blindly. Store the return reason, timestamp, and retry count. And always close the loop with your user—they need to know why their payout failed and what to do next.


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)