Reconciling Like a Ghost: ACH Ops for Third-Party Service Providers
Published on: December 31, 2025

As a Third-Party Service Provider (TPSP), you’re operating in the shadows of the banking system. You don’t have access to the underlying bank balance — just the files you send and the return files you receive. This creates a unique challenge: how do you reconcile your operations when you can’t see the bank’s books?
This article explains how to reconcile via metadata, file logging, and trace ID correlation when you’re operating blind to the actual settlement.
The Ghost Reconciler’s Challenge
TPSPs operate in a world of partial visibility:
- You see your submitted ACH files
- You receive return files and acknowledgments
- You track your own ledger states
- But you never see the actual bank balance
This creates a reconciliation gap that can hide operational issues, compliance problems, and financial discrepancies.
Building Your Ghost Reconciler
1. File-Level Reconciliation
Every ACH file you submit should be logged with:
- File creation timestamp
- Batch number and sequence
- Total entry count and dollar amount
- Expected settlement date
- Trace ID ranges
class ACHFileLogger:
def log_file_submission(self, file_data):
return {
'file_id': generate_uuid(),
'submission_time': datetime.utcnow(),
'batch_count': file_data.batch_count,
'entry_count': file_data.total_entries,
'dollar_amount': file_data.total_amount,
'expected_settlement': calculate_settlement_date(),
'trace_id_start': file_data.trace_id_start,
'trace_id_end': file_data.trace_id_end,
'status': 'submitted'
}
2. Return File Correlation
When return files arrive, correlate them back to your original submissions:
def correlate_returns(return_file, original_files):
"""Match return entries back to original file submissions"""
correlations = []
for return_entry in return_file.entries:
# Find the original file that contained this trace ID
original_file = find_file_by_trace_id(
return_entry.trace_id,
original_files
)
if original_file:
correlations.append({
'return_entry': return_entry,
'original_file': original_file,
'time_to_return': calculate_time_delta(
original_file.submission_time,
return_file.received_time
)
})
return correlations
3. Metadata-Based Reconciliation
Use the metadata from your files to build a reconciliation framework:
class ACHReconciler:
def reconcile_file_cycle(self, file_id):
"""Reconcile a complete ACH file lifecycle"""
file_log = self.get_file_log(file_id)
acknowledgments = self.get_acknowledgments(file_id)
returns = self.get_returns_for_file(file_id)
reconciliation = {
'file_id': file_id,
'submitted_entries': file_log.entry_count,
'acknowledged_entries': acknowledgments.accepted_count,
'rejected_entries': acknowledgments.rejected_count,
'returned_entries': len(returns),
'discrepancy': self.calculate_discrepancy(file_log, acknowledgments, returns)
}
return reconciliation
The Three Pillars of Ghost Reconciliation
Pillar 1: Trace ID Tracking
Every ACH entry has a unique trace ID. Use this to build your reconciliation backbone:
def build_trace_id_map(submitted_files):
"""Create a map of all trace IDs to their source files"""
trace_map = {}
for file in submitted_files:
for entry in file.entries:
trace_map[entry.trace_id] = {
'file_id': file.file_id,
'entry_index': entry.index,
'amount': entry.amount,
'account': entry.account,
'submission_time': file.submission_time
}
return trace_map
Pillar 2: File Acknowledgment Monitoring
Track every acknowledgment to ensure your files were received:
def monitor_file_acknowledgments():
"""Monitor for missing or delayed file acknowledgments"""
expected_acks = get_pending_acknowledgments()
for expected in expected_acks:
if is_acknowledgment_overdue(expected):
alert_operations_team(
f"Missing acknowledgment for file {expected.file_id}"
)
if has_acknowledgment_discrepancy(expected):
flag_reconciliation_issue(expected)
Pillar 3: Return Pattern Analysis
Analyze return patterns to identify systemic issues:
def analyze_return_patterns(returns_data):
"""Identify patterns in ACH returns"""
patterns = {
'return_codes': {},
'time_to_return': [],
'account_patterns': {},
'amount_patterns': {}
}
for return_entry in returns_data:
# Track return code frequency
patterns['return_codes'][return_entry.return_code] = \
patterns['return_codes'].get(return_entry.return_code, 0) + 1
# Track time to return
patterns['time_to_return'].append(return_entry.days_to_return)
# Track account patterns
account = return_entry.account_number[-4:] # Last 4 digits
patterns['account_patterns'][account] = \
patterns['account_patterns'].get(account, 0) + 1
return patterns
Building Your Reconciliation Dashboard
Key Metrics to Track
- File Success Rate: Submitted vs. Acknowledged files
- Entry Success Rate: Submitted vs. Accepted entries
- Return Rate: Entries returned as percentage of accepted
- Time to Return: Average days between submission and return
- Reconciliation Discrepancies: Files that don’t balance
Alerting and Escalation
def setup_reconciliation_alerts():
"""Configure alerts for reconciliation issues"""
alerts = {
'high_return_rate': {
'threshold': 0.05, # 5% return rate
'action': 'immediate_alert'
},
'missing_acknowledgment': {
'threshold': 300, # 5 minutes
'action': 'escalate_to_ops'
},
'reconciliation_mismatch': {
'threshold': 0, # Any mismatch
'action': 'stop_trading'
}
}
return alerts
The Ghost Reconciler’s Toolkit
1. File Comparison Tools
Compare your submitted files with received acknowledgments:
def compare_file_totals(submitted, acknowledged):
"""Compare submitted vs acknowledged file totals"""
discrepancies = []
for submitted_file in submitted:
ack = find_acknowledgment(submitted_file.file_id)
if not ack:
discrepancies.append({
'type': 'missing_acknowledgment',
'file_id': submitted_file.file_id,
'severity': 'critical'
})
continue
if submitted_file.entry_count != ack.accepted_count + ack.rejected_count:
discrepancies.append({
'type': 'entry_count_mismatch',
'file_id': submitted_file.file_id,
'submitted': submitted_file.entry_count,
'acknowledged': ack.accepted_count + ack.rejected_count,
'severity': 'high'
})
return discrepancies
2. Trace ID Validation
Validate that every trace ID in your system can be accounted for:
def validate_trace_id_coverage():
"""Ensure all trace IDs are accounted for"""
submitted_ids = get_all_submitted_trace_ids()
acknowledged_ids = get_all_acknowledged_trace_ids()
returned_ids = get_all_returned_trace_ids()
# All submitted IDs should be either acknowledged or returned
unaccounted = submitted_ids - (acknowledged_ids | returned_ids)
if unaccounted:
log_reconciliation_error(f"Unaccounted trace IDs: {len(unaccounted)}")
return False
return True
3. Settlement Date Tracking
Track expected vs. actual settlement dates:
def track_settlement_dates():
"""Monitor settlement date accuracy"""
expected_settlements = get_expected_settlements()
for expected in expected_settlements:
if is_settlement_overdue(expected):
investigate_settlement_delay(expected)
if has_settlement_discrepancy(expected):
flag_settlement_issue(expected)
Operational Best Practices
1. Daily Reconciliation
Run reconciliation checks daily, not just at month-end:
def daily_reconciliation_check():
"""Perform daily reconciliation checks"""
today = datetime.utcnow().date()
# Check yesterday's files
yesterday_files = get_files_by_date(today - timedelta(days=1))
for file in yesterday_files:
reconciliation = reconcile_file_cycle(file.file_id)
if reconciliation['discrepancy']:
escalate_reconciliation_issue(reconciliation)
2. Real-Time Monitoring
Monitor file acknowledgments in real-time:
def real_time_acknowledgment_monitor():
"""Monitor acknowledgments as they arrive"""
while True:
new_acks = get_new_acknowledgments()
for ack in new_acks:
process_acknowledgment(ack)
if has_acknowledgment_issues(ack):
alert_immediately(ack)
time.sleep(30) # Check every 30 seconds
3. Automated Reconciliation
Automate as much reconciliation as possible:
def automated_reconciliation_pipeline():
"""Automated reconciliation pipeline"""
while True:
# Get new files
new_files = get_new_submitted_files()
for file in new_files:
# Log file submission
log_file_submission(file)
# Set up monitoring
setup_file_monitoring(file)
# Schedule reconciliation checks
schedule_reconciliation_checks(file)
time.sleep(60) # Check every minute
When Reconciliation Fails
Common Failure Modes
- Missing Acknowledgments: Files submitted but never acknowledged
- Entry Count Mismatches: Submitted vs. acknowledged entry counts don’t match
- Return Correlation Failures: Can’t match returns back to original submissions
- Settlement Date Discrepancies: Expected vs. actual settlement dates don’t align
Escalation Procedures
def escalation_procedure(issue):
"""Escalation procedure for reconciliation issues"""
if issue['severity'] == 'critical':
# Stop all trading
stop_trading()
# Alert senior management
alert_senior_management(issue)
# Initiate incident response
initiate_incident_response(issue)
elif issue['severity'] == 'high':
# Alert operations team
alert_operations_team(issue)
# Flag for manual review
flag_for_manual_review(issue)
elif issue['severity'] == 'medium':
# Log for review
log_for_review(issue)
# Monitor for escalation
monitor_for_escalation(issue)
The Ghost Reconciler’s Success Metrics
Key Performance Indicators
- Reconciliation Accuracy: 99.9%+ accuracy in file reconciliation
- Time to Reconciliation: < 24 hours for file-level reconciliation
- Return Correlation Rate: 100% of returns correlated to original submissions
- Alert Response Time: < 5 minutes for critical reconciliation issues
Continuous Improvement
def reconciliation_improvement_loop():
"""Continuous improvement loop for reconciliation"""
while True:
# Analyze reconciliation performance
performance = analyze_reconciliation_performance()
# Identify improvement opportunities
opportunities = identify_improvement_opportunities(performance)
# Implement improvements
for opportunity in opportunities:
implement_improvement(opportunity)
# Measure impact
measure_improvement_impact(opportunities)
# Wait for next cycle
time.sleep(86400) # Daily
Conclusion
Operating as a TPSP means reconciling in the dark, but it doesn’t mean you have to operate blindly. By building robust metadata tracking, file logging, and trace ID correlation systems, you can achieve reconciliation accuracy that rivals direct bank access.
The key is to treat every piece of metadata as a reconciliation anchor point. Every file submission, acknowledgment, and return becomes a data point in your reconciliation framework. When you can’t see the bank’s books, you make your own books so detailed and accurate that they become the source of truth.
Remember: Ghost reconciliation isn’t about seeing what you can’t see — it’s about making what you can see so comprehensive that you don’t need to see the rest.
This article is part of the “How U.S. Payments Really Work” series. View the complete series roadmap to understand the full scope of U.S. payment systems.
Comments & Discussion
Share your thoughts, ask questions, or start a discussion about this article.