Skip to the content.
How U.S. Payments Really Work Part 18
How U.S. Payments Really Work Part 18

ACH Dishonored Returns — Interbank Operational Disputes

Two banks enter, one return leaves 🐭 How to handle R61, R62, and other dishonored return codes in your payment system

Suma Manjunath
Author: Suma Manjunath
Published on: October 08, 2025

Dishnonored ACH Returns

For: Payments engineers, fintech developers, ACH operations teams
Reading Time: 12 minutes
Prerequisites: Familiarity with ACH file formats, NACHA rules, and standard return codes (R01–R33)
Why now: While dishonored returns are rare (<0.1% of all returns), they represent critical operational failures that require immediate attention. Systems without proper handling risk frozen settlements and banking partner escalations.

TL;DR:

⚠️ Critical Distinction: Dishonored returns are interbank operational disputes. They are not the same as consumer disputes under Regulation E.

⚠️ Disclaimer: All scenarios, accounts, names, and data used in examples are fictional and for educational purposes only.


Problem Definition

The challenge: When your ODFI (bank partner) identifies that a return received from an RDFI is invalid, improperly formatted, or misrouted, they must formally dispute it through a dishonored return. Your system needs to recognize these scenarios and trigger appropriate operational workflows.

Who faces this: Fintechs and payment platforms that process high ACH volumes (>10,000 transactions/month) and have direct ACH sponsorship relationships.

Cost of inaction:

Why standard solutions fail: Most ACH integrations only handle the “happy path” return flow (debit → return → done). They don’t model the recursive case where the return itself is disputed.


Understanding Dishonored Returns

The Actual Flow

flowchart TD
  A["Originator sends ACH debit"] --> B["ODFI processes debit"]
  B --> C["ACH Network routes to RDFI"]
  C --> D["RDFI returns transaction (R01, R03, etc.)"]
  D --> E["ODFI receives return"]
  E --> F{"Is return valid?"}
  F -->|"Yes"| G["ODFI processes return normally"]
  F -->|"No - Invalid reason or misrouted"| H["ODFI creates dishonored return entry"]
  H --> I["ODFI sends dishonored return to RDFI (R61, R62, R67–R69)"]
  I --> J["Banks negotiate resolution"]
  J --> K["Final settlement determined"]

💡 Key Point: The ODFI is the one who dishonors (disputes) the RDFI’s return, not the other way around.


Dishonored Return Codes Explained

Code Official Name What It Means Example Scenario Who Initiates
R61 Misrouted Return Return was sent to wrong ODFI RDFI used old routing number; correct ODFI returns it Correct ODFI
R62 Return of Erroneous or Reversing Debit Return itself is erroneous or reverses properly authorized debit RDFI returned an authorized payroll debit as “unauthorized” Original ODFI
R67 Duplicate Return Same return already processed RDFI accidentally submitted return twice ODFI
R68 Untimely Return Return outside allowed timeframe Insufficient funds return sent 5 days late ODFI
R69 Field Error Return has data format errors Trace number missing or malformed Either bank
R70 Permissible Return Entry Not Accepted / Return Not a Duplicate RDFI contests a dishonor Complex dispute scenarios RDFI

Real-World Scenarios

Scenario 1: Misrouted Return (R61)

Situation: Acme Payroll sends a debit via ODFI Bank A (routing: 123456789). The employee changes banks, and the old RDFI returns it for “Account Closed” (R02) — but sends it to the wrong ODFI.

What happens:

Your system’s job:

# R61 - Misrouted Return Example
require 'date'

class ACHReturn
  attr_reader :trace_number, :code, :amount_cents, :received_date

  def initialize(trace_number:, code:, amount_cents:, received_date: Date.today)
    @trace_number = trace_number
    @code = code
    @amount_cents = amount_cents
    @received_date = received_date
  end
end

class DishonoredReturnHandler
  def handle_r61(return_item)
    puts "📬 Received R61 - Misrouted return for trace #{return_item.trace_number}"
    puts "⚙️  No customer impact. Logging and alerting ODFI ops."
    {
      status: 'dishonored',
      code: return_item.code,
      action: 'awaiting_correct_routing'
    }
  end
end

r61 = ACHReturn.new(trace_number: '000000001234567', code: 'R61', amount_cents: 150_000)
handler = DishonoredReturnHandler.new
puts handler.handle_r61(r61)

Scenario 2: Erroneous Return (R62)

Situation: Your platform processes an authorized subscription debit, but the RDFI returns it as unauthorized (R05).

What happens:

Your system’s job:

# R62 - Erroneous Return Example
class ErroneousReturnHandler
  def initialize(odfi_client)
    @odfi_client = odfi_client
  end

  def handle_r62(trace_number, authorization_docs)
    puts "🚨 R62 - Disputing erroneous return for trace #{trace_number}"
    @odfi_client.submit_dishonor_evidence(
      trace_number: trace_number,
      dishonor_code: 'R62',
      documentation: authorization_docs
    )
    puts "📤 Submitted documentation to ODFI. Awaiting RDFI review."
    {
      status: 'under_investigation',
      trace: trace_number,
      action: 'evidence_submitted'
    }
  end
end

class MockODFIClient
  def submit_dishonor_evidence(trace_number:, dishonor_code:, documentation: {})
    puts "Submitting evidence for #{trace_number} with code #{dishonor_code}"
  end
end

auth_docs = {
  authorization_form: 'auth_form_tx001.pdf',
  previous_payments: ['tx_001', 'tx_002'],
  authorization_type: 'recurring_web'
}

handler = ErroneousReturnHandler.new(MockODFIClient.new)
puts handler.handle_r62('000000001234568', auth_docs)

Scenario 3: Duplicate Return (R67)

Situation: RDFI submits a duplicate return for an already processed transaction.

Your system’s job:

# R67 - Duplicate Return Example
class DuplicateReturnHandler
  def handle_r67(trace_number)
    puts "🔁 Duplicate return detected for trace #{trace_number}."
    puts "✅ ODFI rejected duplicate, verified single posting."
    {
      status: 'dishonored',
      code: 'R67',
      trace: trace_number,
      action: 'duplicate_rejected'
    }
  end
end

handler = DuplicateReturnHandler.new
puts handler.handle_r67('000000001234569')

Monitoring & Alerts

# Example Prometheus-compatible metric logging
def record_dishonored_return(code)
  puts "[METRIC] Increment counter ach_dishonored_returns_total{code=\"#{code}\"}"
end

record_dishonored_return('R62')

Prometheus Alert Rules (YAML):

groups:
  - name: ach_dishonored_returns
    rules:
      - alert: HighR62Rate
        expr: rate(ach_dishonored_returns_total{code="R62"}[1h]) > 0.01
        for: 30m
        labels:
          severity: warning
        annotations:
          summary: "High R62 erroneous return rate"
          description: ">1% of returns disputed as erroneous. May indicate authorization problems."

Escalation Guide

Contact your ODFI immediately if:


Validation Checklist


Takeaways


References

  1. NACHA Operating Rules & Guidelines — 2024 Rulebook
  2. NACHA Return Codes Reference — Official Code List, 2024
  3. Federal Reserve — FedACH Operating Circular, 2024
  4. Accuity — ACH Exception Handling Best Practices, 2023

Comments & Discussion

Share your thoughts, ask questions, or start a discussion about this article.