Interbank Settlement and Real-Time Payment Rails
Interbank Settlement and Real-Time Payment Rails
Card payments settle through the card networks. But when you wire $50,000 to a supplier, when your employer direct-deposits your salary, or when a central bank lends to a commercial bank overnight — these movements bypass card networks entirely and flow through interbank payment systems.
These systems face a distributed systems problem that will be familiar: how do you ensure consistency when multiple parties need to agree on the transfer of value, when messages can be delayed or lost, and when participants might default between the time a payment is sent and the time it settles?
The Settlement Problem
When Bank A sends a payment to Bank B, no physical money moves. Instead:
- Bank A instructs the payment system to debit its account at the central bank
- The payment system credits Bank B’s account at the central bank
- Bank B credits its customer’s account
The central bank acts as the trusted intermediary — it holds settlement accounts for all commercial banks, and transfers between these accounts constitute “final settlement.”
But timing creates risk. Consider this scenario:
10:00 AM — Bank A sends €10M payment instruction to Bank B
10:01 AM — Bank B, seeing the instruction, credits Customer B's account
10:02 AM — Bank A is declared insolvent
10:03 AM — The settlement system has not yet debited Bank A's account
Bank B has credited €10M to its customer based on an instruction from a now-insolvent bank. This is settlement risk — the risk that a counterparty defaults between instruction and settlement. The most infamous example is Herstatt Bank (1974), which was closed by German regulators at the end of the European business day, after it had received Deutsche Marks but before it could deliver US Dollars. Its counterparties lost $620 million.
RTGS: Real-Time Gross Settlement
RTGS eliminates settlement risk by settling each payment individually, in real-time, with immediate finality. When the system processes a payment:
- It checks Bank A’s settlement account has sufficient balance
- It atomically debits Bank A and credits Bank B
- The transfer is final and irrevocable — even if Bank A fails one millisecond later
Major RTGS systems:
- Fedwire (US): ~$4 trillion daily volume
- TARGET2 (Eurozone): ~€1.7 trillion daily
- CHAPS (UK): ~£400 billion daily
- BOJ-NET (Japan): ~¥130 trillion daily
from decimal import Decimal
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
class SettlementStatus(Enum):
PENDING = "pending"
SETTLED = "settled"
REJECTED = "rejected"
QUEUED = "queued"
@dataclass
class RTGSPayment:
"""
Model of an RTGS payment instruction.
RTGS systems process each payment as an atomic operation:
either the full amount is transferred, or nothing happens.
There is no partial settlement.
"""
payment_id: str
sender_bank: str # BIC of sending institution
receiver_bank: str # BIC of receiving institution
amount: Decimal
currency: str
value_date: datetime
status: SettlementStatus = SettlementStatus.PENDING
settled_at: datetime | None = None
class RTGSSettlementEngine:
"""
Simplified RTGS settlement engine.
In production, this runs on the central bank's infrastructure
with extremely high availability requirements (99.999%+).
"""
def __init__(self):
self._accounts: dict[str, Decimal] = {}
self._settled: list[RTGSPayment] = []
self._queue: list[RTGSPayment] = []
def process_payment(self, payment: RTGSPayment) -> SettlementStatus:
"""
Process a single payment with immediate finality.
This is the core of RTGS: atomic debit-credit with
no settlement lag. Once this returns SETTLED, the
transfer cannot be reversed (even by the central bank,
except through a new, separate payment).
"""
sender_balance = self._accounts.get(payment.sender_bank, Decimal(0))
if sender_balance < payment.amount:
# Insufficient funds — queue or reject
if self._should_queue(payment):
payment.status = SettlementStatus.QUEUED
self._queue.append(payment)
return SettlementStatus.QUEUED
else:
payment.status = SettlementStatus.REJECTED
return SettlementStatus.REJECTED
# Atomic settlement
self._accounts[payment.sender_bank] -= payment.amount
self._accounts[payment.receiver_bank] = (
self._accounts.get(payment.receiver_bank, Decimal(0)) + payment.amount
)
payment.status = SettlementStatus.SETTLED
payment.settled_at = datetime.utcnow()
self._settled.append(payment)
# Process queued payments that may now have sufficient funds
self._process_queue()
return SettlementStatus.SETTLED
def _should_queue(self, payment: RTGSPayment) -> bool:
"""
Liquidity management: queue payments that might settle
later when incoming payments provide sufficient balance.
Modern RTGS systems use sophisticated queue optimization
algorithms (offsetting, bilateral/multilateral netting of
queued payments) to reduce liquidity requirements.
"""
return True # Queue by default
def _process_queue(self):
"""Try to settle queued payments with available balances."""
still_queued = []
for payment in self._queue:
if self._accounts.get(payment.sender_bank, Decimal(0)) >= payment.amount:
self.process_payment(payment)
else:
still_queued.append(payment)
self._queue = still_queued
The Liquidity Problem
RTGS settles gross (each payment individually), which means banks need to hold large cash reserves at the central bank to cover outgoing payments before incoming payments arrive. On a typical day, a large bank might need $50 billion in reserves to smooth out timing mismatches.
Central banks provide intraday liquidity facilities — essentially interest-free loans during business hours — to keep the system flowing. If Bank A has a $10B balance but needs to send $15B before it receives $20B in incoming payments, the central bank lends the $5B gap.
DNS: Deferred Net Settlement
DNS takes the opposite approach: accumulate all payments during a period (typically a day), calculate the net position of each bank, and settle only the net amounts.
If Bank A sends $100M to Bank B and Bank B sends $80M to Bank A during the day, the net settlement is just $20M from A to B. This requires dramatically less liquidity than settling each payment individually.
class DNSSettlementEngine:
"""
Deferred Net Settlement engine.
Accumulates transactions throughout the clearing cycle,
then calculates and settles net positions at designated
settlement times.
"""
def __init__(self):
self._pending_transactions: list[RTGSPayment] = []
self._settlement_windows: list[str] = [
"09:00", "12:00", "15:00", "17:00" # Multiple windows per day
]
def submit_payment(self, payment: RTGSPayment):
"""
Accept a payment instruction for deferred settlement.
The payment is NOT settled immediately. It enters the
clearing queue and will be included in the next
settlement window.
"""
payment.status = SettlementStatus.PENDING
self._pending_transactions.append(payment)
def calculate_net_positions(self) -> dict[str, Decimal]:
"""
Calculate the net position of each bank.
Positive = bank receives funds (net creditor)
Negative = bank owes funds (net debtor)
The sum of all positions must equal zero (conservation of money).
"""
from collections import defaultdict
positions: dict[str, Decimal] = defaultdict(Decimal)
for txn in self._pending_transactions:
positions[txn.sender_bank] -= txn.amount
positions[txn.receiver_bank] += txn.amount
# Verify conservation: sum must be zero
total = sum(positions.values())
assert total == Decimal(0), f"Conservation violated: {total}"
return dict(positions)
def execute_settlement(self, rtgs: RTGSSettlementEngine):
"""
Settle net positions through the RTGS system.
DNS systems typically use the RTGS system for final settlement
of net positions. This is called "DNS with RTGS settlement."
Only net debtor banks need to fund their positions.
Net creditor banks receive funds.
"""
positions = self.calculate_net_positions()
# Settle: net debtors pay into a central clearing account,
# then the clearing account pays net creditors
clearing_account = "CLEARING_HOUSE"
# Phase 1: Collect from net debtors
for bank, position in positions.items():
if position < 0:
payment = RTGSPayment(
payment_id=f"NET-{bank}",
sender_bank=bank,
receiver_bank=clearing_account,
amount=abs(position),
currency="USD",
value_date=datetime.utcnow()
)
result = rtgs.process_payment(payment)
if result != SettlementStatus.SETTLED:
raise RuntimeError(
f"Settlement failed for {bank}: insufficient funds. "
f"This is a systemic risk event — all participants "
f"in this clearing cycle are affected."
)
# Phase 2: Distribute to net creditors
for bank, position in positions.items():
if position > 0:
payment = RTGSPayment(
payment_id=f"NET-{bank}",
sender_bank=clearing_account,
receiver_bank=bank,
amount=position,
currency="USD",
value_date=datetime.utcnow()
)
rtgs.process_payment(payment)
# Clear the pending transactions
self._pending_transactions.clear()
DNS Risk: Unwinding
The critical weakness of DNS is unwinding risk. If a net debtor bank fails before settlement, the clearing house must recalculate positions without that bank’s payments. This changes everyone’s net position — banks that were net creditors might suddenly become net debtors. In the worst case, the cascade of recalculations causes other banks to fail to meet their new obligations, creating a systemic crisis.
This is why modern DNS systems require participants to post collateral equal to their largest possible net debit position, and why many systems have migrated to hybrid models that combine elements of RTGS and DNS.
Real-Time Payment Systems: The New Wave
A new generation of payment systems operates 24/7/365 with near-instant settlement:
| System | Country | Launched | Speed | Max Amount |
|---|---|---|---|---|
| FedNow | US | 2023 | < 30 sec | $500,000 |
| Faster Payments | UK | 2008 | < 15 sec | £1,000,000 |
| PIX | Brazil | 2020 | < 10 sec | No limit |
| UPI | India | 2016 | < 30 sec | ₹100,000 |
| SEPA Instant | EU | 2017 | < 10 sec | €100,000 |
These systems settle in real-time (like RTGS) but are designed for retail payments (like DNS). The technical challenge is combining the finality guarantees of RTGS with the throughput requirements of a system processing millions of retail payments per day.
FedNow, for example, uses a message-based architecture with ISO 20022 messages. Each payment instruction triggers immediate balance checks, fraud screening, and settlement within the Federal Reserve’s infrastructure — all within a 20-second end-to-end SLA.