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

Understanding ACH Return Codes: A Developer's Guide

When you're building a payout system, ACH returns are inevitable. A user's bank account closes. Funds aren't available. The account holder disputes the transaction. Your code needs to know what happened and respond intelligently.

The National Automated Clearing House Association (Nacha) defines 85 standardized return codes (R01 through R85). Each one tells you exactly why a payment failed—and how to recover.

The Most Common Return Codes

R01: Insufficient Funds
The account doesn't have enough balance. This is the most frequent return you'll see in production. The transaction was valid; timing was wrong.

How to handle it: Retry after 2–3 business days. Implement exponential backoff (day 1, day 3, day 5). If it fails twice, notify the user and offer an alternate payment method.

async function retryAchPayment(payoutId, attempt = 1) {
  const maxRetries = 3;
  const backoffDays = [0, 2, 4]; // Retry on day 2, day 4

  if (attempt > maxRetries) {
    await markPayoutFailed(payoutId, 'max_retries_exceeded');
    return;
  }

  const retryDate = new Date();
  retryDate.setDate(retryDate.getDate() + backoffDays[attempt - 1]);

  await schedulePayoutRetry(payoutId, retryDate, attempt + 1);
}
Enter fullscreen mode Exit fullscreen mode

R03: No Account / Unable to Locate Account
The routing number and account number don't match any open account at that bank. The account was closed, or the user provided incorrect details.

How to handle it: Don't retry. Ask the user to verify their bank details. This is a data-quality issue, not a timing issue.

R10: Unauthorized / Customer Advises Not Authorized
The account holder called their bank and disputed the transaction. Nacha treats this as a customer-initiated chargeback.

How to handle it: Stop retrying immediately. Log the dispute, notify your compliance team, and document the customer's account for future risk assessment.

R29: Corporate Customer Advises Not Authorized
Similar to R10, but initiated by a business customer. Often signals fraud or a legitimate dispute.

How to handle it: Escalate to your fraud team. Pause future payouts to this account until resolved.

R31: Permissible Return Entry (ODFI Only)
The originating bank flagged the entry as problematic before it reached the destination bank. This is rare and usually indicates a malformed ACH file.

How to handle it: Check your ACH batch formatting. Validate account numbers, routing numbers, and entry details against Nacha specs.

Building a Return-Code Handler

Here's a production pattern for decoding and routing ACH returns:

const returnCodeActions = {
  'R01': { retryable: true, maxRetries: 3, backoffDays: [2, 4, 7] },
  'R03': { retryable: false, action: 'request_new_account' },
  'R10': { retryable: false, action: 'escalate_dispute' },
  'R29': { retryable: false, action: 'escalate_dispute' },
  'R31': { retryable: false, action: 'validate_batch_format' },
  'R51': { retryable: false, action: 'request_new_account' }, // Insufficient reserve balance
};

async function handleAchReturn(payoutId, returnCode) {
  const action = returnCodeActions[returnCode];

  if (!action) {
    await logUnknownReturnCode(payoutId, returnCode);
    return;
  }

  if (action.retryable) {
    await retryAchPayment(payoutId, 1, action.backoffDays);
  } else {
    await executeAction(payoutId, action.action);
  }
}
Enter fullscreen mode Exit fullscreen mode

When to Switch Rails

If an ACH return is non-retryable (R03, R10, R29), consider offering an alternate payment method:

  • RTP (Real-Time Payments): Settles in minutes, works for most U.S. banks. Higher cost (~$0.50–$1.00 per transaction).
  • Visa Direct: Faster than ACH (~30 min), but limited to Visa debit cards.
  • Wire Transfer: Guaranteed delivery, but expensive ($15–$50) and irreversible.

Timing Matters

ACH returns typically arrive 2–5 business days after the original debit. Your reconciliation logic must account for this lag. Don't mark a payout as "successful" until you've waited 5+ business days without a return.

Key Takeaway

ACH


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)