ACH Return Codes Explained: R01–R85 and How to Handle Them
ACH Return Codes Explained: R01–R85 and How to Handle Them
When an ACH transaction fails, the originating bank receives a return code. Understanding what each code means—and how to respond—is critical for any developer building payout systems, payroll integrations, or marketplace payment flows.
Unlike credit card declines, which happen in milliseconds, ACH returns arrive 1–5 business days after the transaction posts. By then, your system may have already marked the payout as complete. A well-designed integration catches these codes, logs them, and decides whether to retry, escalate, or route to an alternate payment rail.
The Most Common ACH Return Codes
The National Automated Clearing House Association (Nacha) maintains the official R-code set. Here are the ones you'll encounter most often:
| Code | Meaning | Cause | Retry? |
|---|---|---|---|
| R01 | Insufficient funds | Account balance too low | Yes (after funds available) |
| R03 | No account / unable to locate | Account closed or number wrong | No |
| R04 | Invalid account number | Malformed or fake account | No |
| R05 | Account closed by institution | Bank closed the account | No |
| R07 | Authorization revoked | Receiver revoked permission | No |
| R10 | Customer advises not authorized | Receiver claims they didn't authorize | No |
| R29 | Corporate customer advises not authorized | Business receiver disputes | No |
| R31 | Permissible return by receiver | Receiver requested return (soft return) | Maybe |
| R51 | Receiver does not participate in ACH | Rare; account not ACH-eligible | No |
| R82 | Non-transaction account | Savings account, not checking | No |
Return codes R01–R85 cover everything from technical failures (R02: route not found) to fraud disputes (R10, R29) to settlement issues (R82: wrong account type).
Handling R01: Insufficient Funds
R01 is the most common return and the most likely to succeed on retry.
async function handleAchReturn(returnCode, payoutId, recipientAccount) {
if (returnCode === 'R01') {
// Insufficient funds — likely temporary
const payout = await db.getPayoutById(payoutId);
// Schedule retry in 3 business days
await db.updatePayout(payoutId, {
status: 'pending_retry',
nextRetryDate: addBusinessDays(new Date(), 3),
returnCode: 'R01',
attemptCount: payout.attemptCount + 1
});
// Log for monitoring
logger.info(`R01 return for payout ${payoutId}; retry scheduled`);
// Optionally notify recipient
await notificationService.sendEmail(payout.recipientEmail,
'Your payout is pending. We'll retry in 3 business days.');
return { action: 'retry_scheduled' };
}
}
Retry R01 up to 3 times, spaced 3–5 business days apart. After that, escalate to manual review.
Handling R03, R04, R05: Account Issues
These codes indicate the account itself is invalid or closed. Do not retry.
if (['R03', 'R04', 'R05'].includes(returnCode)) {
// Account is bad; need new banking details
await db.updatePayout(payoutId, {
status: 'failed_account_invalid',
returnCode: returnCode
});
// Notify recipient to update account
await notificationService.sendSMS(payout.recipientPhone,
`Your payout failed (${returnCode}). Please update your bank details.`);
// Flag for manual review if high-value payout
if (payout.amount > 10000) {
await escalationQueue.add({
payoutId,
reason: 'Account invalid; manual review needed'
});
}
return { action: 'failed_account_invalid', requiresManualReview: true };
}
Handling R10, R29: Unauthorized Claims
R10 (customer disputes authorization) and R29 (corporate customer disputes) are fraud-adjacent. The receiver is claiming they never approved the payout.
javascript
if (['R10', 'R29'].includes(returnCode)) {
// Potential fraud or permission issue
await db.updatePayout(payoutId, {
status: 'disputed',
returnCode: returnCode,
flaggedForFraudReview: true
});
// Notify compliance team
await complianceQueue.add({
---
*Decoding ACH return codes programmatically? The [ACH Return Codes API](https://rapidapi.com/payoutrail-ach-return-codes/api/ach-return-codes-api?utm_source=nichestream&utm_medium=devto&utm_campaign=payoutrail-ach-returns) returns the full Nacha R01–R85 set with plain-language descriptions and handling guidance.*
Top comments (0)