Skip to main content

Reconciliation

Overview

Reconciliation in payments is the process of matching internal payment records against external confirmations (bank statements, network confirmations, settlement reports) to ensure every transaction is correctly accounted for, with no gaps, duplicates, or errors.


Why Reconciliation Matters

Risk if not reconciledImpact
Undetected failed paymentsCustomer funds lost or not delivered
Duplicate paymentsDouble payment to supplier
Settlement shortfallBank exposed to overnight liquidity risk
Unidentified creditsFunds sitting in suspense, not applied
Regulatory breachFailure to report or account for transactions

Reconciliation Types

1. Payment Reconciliation (Transaction Level)

Match each internal payment order against network/bank confirmation:

Internal Payment Order  ←→  pacs.002 / pain.002 confirmation
EndToEndId OrgnlEndToEndId
TxId OrgnlTxId
Amount IntrBkSttlmAmt
Status TxSts

2. Account/Ledger Reconciliation

Match posted ledger entries against camt.053 bank statement:

Internal Ledger Entry  ←→  camt.053 Ntry
LedgerEntryId AcctSvcrRef
Amount Amt
BookingDate BookgDt
CdtDbtInd CdtDbtInd

3. Nostro Reconciliation

Match FI's own ledger of a correspondent account against the correspondent bank's statement:

Our Nostro Ledger  ←→  MT940 / camt.053 from correspondent

4. Settlement Reconciliation

Verify actual settlement movements at the central bank (ESA):

Expected net settlement position  ←→  RBA FSS settlement confirmation

Reconciliation Data Sources

SourceMessageContent
Internal payment systemN/APayment orders, statuses
NPP Networkpacs.002Per-transaction confirmation
SWIFTpacs.002 / MT199Status per payment
Bank statement (own)camt.053 / MT940All entries for period
Intraday notificationcamt.054Real-time transaction alerts
Settlement confirmationRBA FSS reportNet position settled

Reconciliation Process

End of Business Day


Collect all internal payment records for the day


Receive camt.053 from correspondent/internal bank


For each internal record:
Search for matching camt.053 entry

├── MATCHED → Check amount, date, currency
│ └── All match → RECONCILED ✅
│ └── Mismatch → FLAG for investigation

└── UNMATCHED → Check pacs.002 status
├── RJCT → Reverse and reconcile
├── PDNG → Carry forward
└── No response → Escalate


Balance check:
Statement opening + credits − debits = closing?

├── YES → Reconciliation complete ✅
└── NO → Investigate discrepancy

Match Keys (IDs Used for Reconciliation)

IDSet ByCarried InUsed For
EndToEndIdOriginating customerpain.001, pacs.008, camt.054E2E payment tracking
TxIdDebtor bankpacs.008, pacs.002Interbank transaction matching
InstrIdSending bankpacs.008Instruction-level matching
AcctSvcrRefCreditor bankcamt.053, camt.054Statement entry matching
UETRDebtor bankAll SWIFT messagesSWIFT payment tracking
MsgIdSenderAll messagesMessage-level dedup

Suspense Account

The suspense account is a holding account for transactions that cannot be immediately matched or applied:

Reasons for suspense:
- Inbound payment — creditor account not found
- Credit or debit with no matching payment order
- Amount mismatch
- Pending investigation (fraud, sanctions)

Suspense management:
- All suspense items must be aged and reviewed daily
- SLA: resolve within 5 business days
- Unresolved items escalated to operations management
- Regulatory obligation: cannot hold funds indefinitely

Reconciliation Statuses

StatusMeaning
MATCHEDInternal record matched to external confirmation
RECONCILEDMatched and verified (amount, date, status correct)
UNMATCHEDNo corresponding external record found
DISPUTEDMatch found but data doesn't agree
PENDINGAwaiting confirmation (e.g., T+1 settlement)
WRITTEN_OFFUnrecoverable difference; actioned by finance
IN_SUSPENSEHeld pending investigation

Java Spring Reconciliation Service

@Service
public class ReconciliationService {

@Scheduled(cron = "0 0 22 * * MON-FRI") // 10 PM weekdays
public void runDailyReconciliation() {
LocalDate today = LocalDate.now();

// 1. Get all internal payment orders for today
List<PaymentOrder> orders = paymentRepository.findByDate(today);

// 2. Get camt.053 statement entries
List<StatementEntry> entries = statementRepository.findByDate(today);

// 3. Match
ReconciliationReport report = matcher.match(orders, entries);

// 4. Handle unmatched
report.getUnmatched().forEach(unmatched -> {
suspenseService.createItem(unmatched);
alertService.notifyOpsTeam(unmatched);
});

// 5. Handle mismatches
report.getMismatches().forEach(mismatch -> {
investigationService.raise(mismatch);
});

// 6. Publish report
reportingService.publish(report);
}
}

@Component
public class PaymentMatcher {

public ReconciliationReport match(
List<PaymentOrder> orders,
List<StatementEntry> entries) {

Map<String, StatementEntry> entryByRef = entries.stream()
.collect(Collectors.toMap(
StatementEntry::getAcctSvcrRef,
Function.identity()
));

var report = new ReconciliationReport();

for (PaymentOrder order : orders) {
// Try matching by EndToEndId first, then TxId
Optional<StatementEntry> match = findMatch(order, entryByRef);

if (match.isEmpty()) {
report.addUnmatched(order);
} else if (!amountsMatch(order, match.get())) {
report.addMismatch(order, match.get());
} else {
report.addReconciled(order, match.get());
}
}

return report;
}
}

Reconciliation SLAs

TypeSLAEscalation
NPP real-timeIntradayWithin 4 hours if unmatched
DE/BECSNext business dayWithin 24 hours
SWIFTT+1 to T+2Within 3 business days
Suspense items5 business daysFinance director sign-off
Nostro breaksEnd of dayTreasury escalation