Skip to main content
digital payment systems cryptography banking protocols and blockchain internals

Interbank Settlement and Real-Time Payment Rails

8 min read Chapter 7 of 21

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?

RTGS vs DNS Settlement

The Settlement Problem

When Bank A sends a payment to Bank B, no physical money moves. Instead:

  1. Bank A instructs the payment system to debit its account at the central bank
  2. The payment system credits Bank B’s account at the central bank
  3. 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:

  1. It checks Bank A’s settlement account has sufficient balance
  2. It atomically debits Bank A and credits Bank B
  3. 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:

SystemCountryLaunchedSpeedMax Amount
FedNowUS2023< 30 sec$500,000
Faster PaymentsUK2008< 15 sec£1,000,000
PIXBrazil2020< 10 secNo limit
UPIIndia2016< 30 sec₹100,000
SEPA InstantEU2017< 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.