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

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

When an ACH transfer fails, your payout system receives a return code. Understanding what each code means—and how to respond—is critical for building reliable fintech integrations. This guide maps the real Nacha return-code set to practical developer workflows.

Why ACH Returns Matter

ACH (Automated Clearing House) is the backbone of U.S. domestic payouts: low-cost, batch-based, and reversible. But reversibility cuts both ways. A transfer can be rejected or recalled up to five business days after initiation. Your code must detect, classify, and act on these returns without breaking the payout flow.

The Nacha Return Code Set (R01–R85)

Nacha defines 85 return codes. Here are the most common ones you'll encounter:

Code Reason Recoverable? Action
R01 Insufficient funds Yes Retry after 2–3 days or route to alternate rail
R03 No account / unable to locate No Mark recipient invalid; contact user
R04 Invalid account number No Validate account format before next attempt
R05 Account closed No Update recipient record; flag for manual review
R07 Authorization revoked No Contact recipient; request new authorization
R08 Payment stopped Yes Retry or escalate to user
R10 Customer advises unauthorized No Investigate; may indicate fraud or dispute
R14 Representative payee deceased No Verify recipient status
R16 Account frozen No Contact recipient's bank
R20 Non-transaction account No Confirm account type supports ACH
R29 Corporate customer advises not authorized No Verify authorization chain

How to Decode a Return in Code

When your ODFI (Originating Depository Financial Institution) or processor returns a file, parse the return code and route it:

async function handleAchReturn(returnRecord) {
  const { transactionId, returnCode, amount, recipientId } = returnRecord;

  const nonRecoverableCodes = ['R03', 'R04', 'R05', 'R07', 'R10', 'R14', 'R16', 'R20', 'R29'];
  const recoverableCodes = ['R01', 'R08'];

  if (nonRecoverableCodes.includes(returnCode)) {
    // Log, alert ops, mark recipient as invalid
    await db.recipients.update(recipientId, { status: 'invalid', returnCode });
    await notifyUser(recipientId, `ACH failed: ${returnCode}. Contact support.`);
    return { action: 'manual_review', retry: false };
  }

  if (recoverableCodes.includes(returnCode)) {
    // Schedule retry after 2–3 business days
    await scheduleRetry(transactionId, { delayDays: 3, maxAttempts: 2 });
    return { action: 'retry_scheduled', nextAttempt: addDays(new Date(), 3) };
  }

  // Unknown code: escalate
  await escalateToOps(transactionId, returnCode);
  return { action: 'escalated' };
}
Enter fullscreen mode Exit fullscreen mode

Timing and Reconciliation

ACH returns arrive in batches, typically:

  • Same-day ACH returns: within hours (R03, R04, R05 often detected early)
  • Next-day returns: standard ACH (R01, R08 may take 1–2 days)
  • Delayed returns: up to 5 business days (rare, but possible)

Your reconciliation must account for this window. A transaction marked "sent" should not be marked "settled" until the return window closes (usually day 5). If you use same-day ACH, return windows are shorter but your settlement expectations change.

Building Retry Logic

Not all returns warrant a retry. Use this pattern:

const retryPolicy = {
  R01: { retryable: true, delayDays: 3, maxAttempts: 2 }, // Insufficient funds
  R08: { retryable: true, delayDays: 2, maxAttempts: 1 }, // Payment stopped
  R03: { retryable: false }, // No account
  R10: { retryable: false }, // Unauthorized
};

async function decideRetry(returnCode, attemptCount) {
  const policy = retryPolicy[returnCode];
  if (!policy) return false;
  return policy.retryable && attemptCount < policy.maxAttempts;
}
Enter fullscreen mode Exit fullscreen mode

When to Route to an Alternate Rail


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)