NACHA File Demystified: Cracking the 94-Character Code
The 94-character lines that power $72 trillion in ACH transactions annually.

Audience: Fintech engineers, payroll system builders, backend developers working with ACH
Reading Time: 12 minutes
Prerequisites: Basic understanding of bank accounts and ACH money flow
Why Now: $72 trillion moves through ACH each year. If you build fintech in the U.S., you will touch NACHA files. Getting it wrong means payroll failures, rejected batches, and angry CFOs.
TL;DR:
- NACHA files are rigid 94-character fixed-width records; one misplaced space causes rejection
- Think of them as financial DNA: headers = envelope, entries = content, addenda = extra notes
- ✅ Learn to generate valid files with working Ruby code and realistic values
- 🛠️ Validate files with hash totals, padding, and NACHA validation tools before submission
⚠️ Disclaimer: All scenarios, accounts, names, and data used in examples are not real. They are realistic scenarios provided only for educational and illustrative purposes.
Problem Definition
The challenge: Engineers building payroll or B2B payments eventually face NACHA rejections like “Invalid field length at position 47.” Debugging is painful because every character must align exactly.
Who faces this:
- Payroll SaaS teams generating ACH files
- Fintechs integrating with banks
- Engineers working with B2B/utility billing
Cost of inaction: Rejected batches mean missed payrolls, delayed settlements, regulatory risk, and customer churn.
Why current tutorials fail: Most explanations are abstract (“fixed-width, 94 characters”) without complete, runnable examples. Developers are left guessing about padding, routing numbers, or totals.
Solution Implementation
The Mail Analogy
NACHA files are like mail delivery:
- File Header/Footer (lines 1 & 9) → The envelope
- Batch Header/Footer (lines 5 & 8) → Each letter inside
- Entry Details (line 6) → The actual message (transaction)
- Addenda Records (line 7) → Extra pages, corrections, or receipts
- Filler Lines (all 9s) → Junk mail! They’re only there so the post office (ACH system) always sorts in blocks of 10 letters. Because even money moves in batches. 📬💸
Here’s a Mermaid diagram of the structure:
flowchart TD
A["File Header (1)"] --> B["Batch Header (5)"]
B --> C1["Entry Detail (6)"]
B --> C2["Entry Detail (6)"]
C1 --> D["Addenda (7, optional)"]
C2 --> D
B --> E["Batch Control (8)"]
A --> F["File Control (9)"]
F --> G["Filler Lines (all 9s)"]
Complete Ruby NACHA File Generator
require 'date'
class NachaFileBuilder
def initialize(company_name:, company_id:, destination_bank:)
@company_name = company_name
@company_id = company_id
@destination_bank = destination_bank
@entries = []
@batch_number = 1
end
def add_credit(account_number:, routing_number:, amount_cents:, name:, individual_id:)
@entries << {
transaction_code: '22', # Credit to checking
routing_number: routing_number,
account_number: account_number.ljust(17),
amount: sprintf('%010d', amount_cents),
name: name.ljust(22)[0,22],
individual_id: individual_id
}
end
def generate_file
lines = []
lines << file_header_record
lines << batch_header_record
@entries.each_with_index do |entry, idx|
lines << entry_detail_record(entry, idx + 1)
end
lines << batch_control_record
lines << file_control_record
# Pad to multiple of 10 records
while lines.length % 10 != 0
lines << "9" * 94
end
lines.join("\n")
end
private
def file_header_record
[
'1', '01',
@destination_bank.ljust(10),
@company_id.rjust(10, '0'),
Date.today.strftime('%y%m%d'),
Time.now.strftime('%H%M'), 'A',
'094', '10', '1',
'BANK OF AMERICA'.ljust(23),
@company_name.ljust(23),
' ' * 8
].join('').ljust(94)
end
def batch_header_record
"5220#{@company_name.ljust(16)}#{@company_id.ljust(10)}PPDPAYROLL #{Date.today.strftime('%y%m%d')} 10610000520000001".ljust(94)
end
def entry_detail_record(entry, trace_number)
[
'6', entry[:transaction_code],
entry[:routing_number][0,8],
entry[:account_number],
entry[:amount],
entry[:name],
' ',
entry[:individual_id].ljust(15),
sprintf('%07d', trace_number)
].join('').ljust(94)
end
def batch_control_record
total_credit = @entries.sum { |e| e[:amount].to_i }
count = @entries.length
hash = @entries.sum { |e| e[:routing_number][0,8].to_i }.to_s[-10..]
"8220000#{count.to_s.rjust(4,'0')}#{hash}0000000000#{sprintf('%010d', total_credit)}#{@company_id.ljust(10)}06100005#{@batch_number.to_s.rjust(7,'0')}".ljust(94)
end
def file_control_record
"9000001#{@batch_number.to_s.rjust(6,'0')}000000#{@entries.length.to_s.rjust(6,'0')}#{'0'*22}".ljust(94)
end
end
# --- Usage Example ---
builder = NachaFileBuilder.new(company_name: "ACME CORP", company_id: "123456789", destination_bank: "061000052")
builder.add_credit(account_number: "123456789", routing_number: "061000052", amount_cents: 12500, name: "JOHN DOE", individual_id: "EMP001")
puts builder.generate_file
ℹ️ Note: This produces a valid 10-line NACHA file padded with 9
s.
❗ Warning: If you misalign spacing or routing digits, the ODFI will reject the entire batch.
💡 Tip: Always validate your generated file with your ODFI’s sandbox tools before submitting to production.
Validation & Monitoring
Testing Steps
- Save output to
sample.ach
. - Run through a NACHA validator (provided by your ODFI).
- Check:
- Line count multiple of 10
- Entry Hash = sum of first 8 digits of routing numbers
- Debit and credit totals balance
Success Metrics
- File accepted by ODFI (no structural errors)
- Matching Entry Hash totals
- Transactions settle on expected effective date
Common Failure Modes
- ❌ Decimal amounts (
500.00
instead of0000050000
) - ❌ Short account numbers (must be space-padded to 17 chars)
- ❌ Wrong date format (
2025-08-15
instead of250815
)
Troubleshooting
- Use a hex editor to check invisible spaces
- Cross-verify totals with a manual calculator
- Compare line length → every line must be exactly 94 chars
Key Takeaways
- Every character matters: One space off = full batch rejection
- Fixed-width wins: Faster parsing, easier auditing, and legacy compatibility
- Always validate totals: Entry Hash + credit/debit sums = mathematical proof
- Use realistic test data: Routing
061000052
, account123456789
, cents amounts - Read NACHA spec: The 500+ page rulebook is the final word
Acronyms
- ACH – Automated Clearing House
- ODFI – Originating Depository Financial Institution
- RDFI – Receiving Depository Financial Institution
- NACHA – National Automated Clearing House Association
References
- NACHA. “ACH Operating Rules & Guidelines.” NACHA, 2024 - nacha.org
- Federal Reserve. “FedACH Services.”
Comments & Discussion
Share your thoughts, ask questions, or start a discussion about this article.