At a glance#
| Number | Value | What it drives |
|---|---|---|
| Transactions | ~434B / year (2025, both networks) | ~1.19B a day · ~13,800 TPS average |
| Peak | ~55,000 TPS (4× on Black Friday) | build for ~100,000; VisaNet tested at 65,000+ msg/s |
| Latency | < 2 s end to end · < 200 ms network | tiered issuer timeouts, ~1 s in all |
| Fraud scoring | < 1 ms · 500+ attributes | features in memory, no network calls |
| Issuers | ~14,500 banks (Visa) | BIN table in RAM, per-issuer stand-in rules |
| Message | ~600 B encrypted ISO 8583 | ~1 Gbps per data centre at 100K TPS |
| Storage | ~3.5 KB / txn → ~4.2 TB / day | ~32 PB with 7-year retention and 3× replication |
| Availability | 99.9999 % (~32 s a year) | multi-DC, stand-in, never double-charge |
Architecture#
DiagramThe network on one page: acquirer → switch → issuer, money settles later
%% title: The network on one page: acquirer → switch → issuer, money settles later
flowchart TD
POS@{ icon: "sd:card", label: "POS terminal · online checkout<br/>175M+ merchant locations" } --> ACQ@{ icon: "sd:bank", label: "Acquirer + processor<br/>merchant's bank · builds ISO 8583" }
ACQ ==>|"leased lines · MPLS VPN"| MG["Message gateway<br/>parse ISO 8583 · decrypt fields · idempotency"]:::edge
MG ==> RS["Authorization switch<br/>BIN lookup in RAM · WAL · tiered timeouts"]:::hot
RS --> FD["Fraud scorer<br/>500+ attributes · < 1 ms · score 0–99"]:::core
FD --> RD@{ icon: "tech:redis", label: "Redis Cluster<br/>per-card velocity · sharded by PAN hash" }
RS ==>|"request + risk score"| ISS@{ icon: "sd:bank", label: "Issuer gateway + core banking<br/>approves or declines" }
ISS -.->|"no answer in budget"| SIP["Stand-in processing<br/>issuer's rules · queued to reconcile"]:::service
RS --> KF@{ icon: "tech:kafka", label: "Kafka<br/>transaction events" }
KF --> PG@{ icon: "tech:postgresql", label: "PostgreSQL<br/>authorization records" }
PG -->|"clearing files matched to auths"| CLR["Clearing + settlement<br/>interchange · net positions"]:::service
CLR -.->|"T+1 to T+2"| SET@{ icon: "sd:building", label: "Banks settle net positions<br/>Fedwire · SWIFT" }
class POS client
class ACQ,ISS external
class RD cache
class KF queue
class PG,SET store- The issuer decides; the network routes by BIN, adds a fraud score, and stands in only when the issuer can't answer.
- Authorization moves no money. Clearing and settlement run as batches off the hot path.
DiagramInside the switch: one ID, an overlapped WAL, tiered timeouts
%% title: Inside the switch: one ID, an overlapped WAL, tiered timeouts
flowchart TD
REQ["Auth request<br/>acquirer ID + txn ID"]:::edge --> ID{"Seen this key<br/>in the last 24 h?"}
ID -->|"yes"| CACHED["Return the stored response<br/>no second charge"]:::ok
ID -->|"no"| WORK["WAL append starts, overlapped<br/>BIN < 1 μs · fraud score < 1 ms"]:::core
WORK --> T1["Primary issuer endpoint · 500 ms"]:::service
T1 -->|"timeout"| T2["Backup endpoint · 300 ms"]:::service
T2 -->|"timeout"| T3["Stand-in · 200 ms<br/>e.g. ≤ $500 · ≤ $2,000 a day · score ≤ 80 · not lost or stolen"]:::queue
T1 -->|"answer"| RESP["Respond after the WAL commit<br/>cache it under the key"]:::ok
T2 -->|"answer"| RESP
T3 --> RESPKey flows#
DiagramAuthorization: tap to approval in under 2 s
%% title: Authorization: tap to approval in under 2 s sequenceDiagram autonumber participant P as POS terminal participant A as Acquirer participant N as Network switch participant I as Issuer P->>A: card data · amount · ISO 8583 A->>N: auth request · acquirer ID + txn ID Note over N: idempotency check · BIN → issuer · WAL append starts N->>N: fraud score in under 1 ms N->>I: request + risk score I->>I: check limit and own fraud rules I-->>N: approve · auth code N-->>A: approve (after WAL commit) A-->>P: approved · receipt Note over P,I: no money has moved · network share under 200 ms
DiagramIssuer silent, then the acquirer retries
%% title: Issuer silent, then the acquirer retries
sequenceDiagram
autonumber
participant A as Acquirer
participant N as Network switch
participant I as Issuer
A->>N: auth request (txn T1)
N->>I: primary endpoint
I--xN: no answer in 500 ms
N->>I: backup endpoint
I--xN: no answer in 300 ms
N->>N: stand-in on issuer rules (200 ms)
alt within limits, score ok, card not flagged
N-->>A: approved by stand-in
else outside the rules
N-->>A: declined
end
Note over N,I: stand-in decision queued · issuer reconciles and owns it
A->>N: retry T1 after its own timeout
N-->>A: same stored response · no second chargeDiagramClearing and settlement: T+0 to T+2
%% title: Clearing and settlement: T+0 to T+2
sequenceDiagram
autonumber
participant M as Merchant
participant A as Acquirer
participant N as Network
participant I as Issuer
M->>A: end of day · batch of the day's sales
A->>N: clearing file
N->>N: match each record to its auth · interchange · net per bank
alt amount differs from auth (tip, fuel, hotel)
N->>N: accept within tolerance · over it, notify issuer
end
N->>A: settlement file · net credit
N->>I: settlement file · net debit
I->>N: one net transfer (Fedwire or SWIFT)
N->>A: one net transfer
A->>M: merchant credited, less fees
Note over M,I: e.g. $100 sale: issuer $2.00 · network $0.20 · acquirer $0.30 · merchant $97.50Data model#
DiagramCore entities
%% title: Core entities
classDiagram
class Authorization {
+txnId : string
+acquirerId : string · with txnId, idempotency key
+panHash : string
+amount : minor units
+currency : ISO code
+mcc : 4 digits
+responseCode : 00 / 51 / 59 / 91
+riskScore : 0–99
+standIn : bool
}
class IssuerRoute {
+bin : 6–8 digits
+primaryEndpoint : string
+backupEndpoint : string
+timeoutMs : int
+supportsStandIn : bool
}
class StandInRules {
+singleTxnLimit : money
+dailyLimit : money
+maxRiskScore : int
}
class Velocity {
+panHash : string · shard key
+txns : sorted set by time
+amounts : sorted set by time
+merchants : sorted set by time
}
class ClearingRecord {
+txnId : string
+authCode : string
+amount : may differ from auth
+interchangeFee : money
}
class SettlementPosition {
+bankId : string
+netAmount : plus receives, minus pays
+txnCount : int
}
Authorization "many" --> "1" IssuerRoute : routed by BIN
IssuerRoute "1" --> "1" StandInRules : issuer sets
Authorization "many" --> "1" Velocity : updates
Authorization "1" --> "0..1" ClearingRecord : cleared as
SettlementPosition "1" --> "many" ClearingRecord : nets- ~1 KB auth + ~2 KB clearing + ~0.5 KB settlement per transaction: ~4.2 TB a day, ~1.5 PB a year.
- BIN routes are read replicas in RAM everywhere; velocity shards by hashed PAN over a 48 h window.
Lifecycle#
DiagramAuthorization state machine
%% title: Authorization state machine stateDiagram-v2 [*] --> RECEIVED RECEIVED --> SCORED: new key · fraud score RECEIVED --> REPLAYED: key seen · cached answer SCORED --> AT_ISSUER: routed by BIN AT_ISSUER --> APPROVED: issuer approves AT_ISSUER --> DECLINED: issuer declines AT_ISSUER --> STAND_IN: primary + backup time out STAND_IN --> APPROVED: within issuer rules STAND_IN --> DECLINED: outside rules APPROVED --> CLEARED: T+1 batch, matched CLEARED --> SETTLED: net transfer, T+1 to T+2 CLEARED --> DISPUTED: chargeback in window SETTLED --> [*] DECLINED --> [*] REPLAYED --> [*] DISPUTED --> [*] class SETTLED ok class DECLINED,DISPUTED bad class STAND_IN queue
Decisions#
| Decision | Chose | Over | Because |
|---|---|---|---|
| Network's role | Route messages; banks hold money | Holding funds | no funds at risk; scales as a router |
| Moving money | Batch clearing, net settlement T+1/T+2 | Transfer per swipe | one net transfer per bank, not millions |
| Issuer unreachable | Stand-in on issuer rules | Decline, or wait | shopper gets an answer; risk stays bounded |
| Retries | Key = acquirer ID + original txn ID, 24 h cache | Resend to issuer | never double-charge |
| Durability | WAL overlapped; response waits for it | Write first, or after replying | no answered auth lost, no added latency |
| Replication | Sync to regional peer, async across regions | Sync everywhere | fits the budget; WAL catches peers up |
| Fraud features | In-memory velocity, one pipelined read | DB query per auth | under 1 ms |
When it breaks#
| Failure | What users see | Contain it with |
|---|---|---|
| Issuer slow on Black Friday | an answer within ~1 s | stand-in for that issuer, reconcile later |
| Acquirer retries after a timeout | one charge | cached response under the idempotency key |
| Data centre outage | a blip | route to the backup DC, failover < 30 s (target) |
| Redis velocity cluster fails | payments continue | fall back to conservative limits |
| Fraud model overruns 1 ms | no delay | proceed on rules |
| Card-testing botnet across merchants | honest shoppers unaffected | count per BIN, merchant, device; 3-D Secure on affected card-not-present BINs; tell issuers |
Say it in the interview#
- "The network moves messages, banks move money." It never holds funds, so it scales as a router with batch settlement behind it.
- Authorization is a promise, settlement is the payment: real time for the shopper, netted batches for the banks.
- Spend the timeout in tiers: primary 500 ms, backup 300 ms, stand-in 200 ms, all inside ~1 s.
- Same key, same answer: acquirer ID plus the original txn ID makes every retry safe.
- Nothing slow on the hot path: BIN table in RAM, fraud features in memory, the WAL overlapped with the work.
- Degrade, don't stop: velocity store down → conservative limits; model slow → rules; issuer down → stand-in.
- Only the network sees the whole pattern: card testing spread over thousands of merchants shows up per BIN range, not per card.