Table of Contents

In global payment processing and fintech infrastructure (Visa, Mastercard, Stripe, Adyen, PayPal), evaluating fraud risk is one of the most demanding real-time engineering challenges in computer science [1].

When a consumer swipes a credit card or clicks "Pay Now" on an e-commerce checkout, the payment network imposes a strict global authorization round-trip deadline of $50\text{ to }100\text{ milliseconds}$.

Within that razor-thin budget, the internal fraud detection platform has an allocated execution ceiling of $\le 10\text{ milliseconds}$.

During those 10 milliseconds, the system must ingest the transaction event, compute sliding-window velocity metrics, query in-memory Graph Feature Stores for stolen card rings, execute a machine learning ensemble (XGBoost / Deep Neural Networks), and enforce deterministic regulatory compliance rules.

This deep-dive architectural guide explores the high-throughput, low-latency streaming infrastructure that powers sub-10ms fraud decisioning at planetary scale.

flowchart TD subgraph SG1_RealTimeFraud ["Real-Time Fraud Decisioning Pipeline (<= 10ms SLA)"] TxEvent["Transaction Ingestion Event: 50k tx/sec"] --> Kafka["Apache Kafka Stream"] subgraph SG2_ParallelStatefulFeature ["Parallel Stateful Feature Computation (2-4ms)"] Kafka --> Flink["1. Apache Flink: Stateful Sliding Velocity Windows (RocksDB Backend)"] Kafka --> GraphStore["2. In-Memory Graph Feature Store (Device Flink --> IP Ring Detection)"] end Flink --> FeatureAggregator["Real-Time Feature Vector Assembly"] GraphStore --> FeatureAggregator subgraph SG3_LowLatencyDecision ["Low-Latency Decision Core (3-5ms)"] FeatureAggregator --> DeterministicRules["3. Deterministic Hard Rules Engine (OFAC, Impossible Velocity)"] FeatureAggregator --> MLInference["4. Sub-Millisecond ML Ensemble (Treelite / ONNX Engine)"] end DeterministicRules --> DecisionGate["Unified Risk Score DeterministicRules --> Decision: APPROVE / DECLINE / 3DS CHALLENGE"] MLInference --> DecisionGate MLInference --> Decision end classDef green fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d; classDef red fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#7f1d1d; classDef blue fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef yellow fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef purple fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#4c1d95; class TxEvent,DeterministicRules blue class Kafka,MLInference green class Flink,DecisionGate purple class GraphStore yellow class FeatureAggregator red

1. The 10ms Authorization SLA Budget Breakdown

To prevent checkout timeouts, the internal payment gateway allocates strict time budgets across the authorization pipeline:

> **100ms PAYMENT AUTHORIZATION BUDGET TIMELINE**
|  0ms - 25ms   : Client Mobile/Web Network Hop to Edge API Gateway                     |
|  25ms - 35ms  : Token Decryption, Idempotency Check & Account Routing                 |
|  35ms - 45ms  : >>> REAL-TIME FRAUD DETECTION PIPELINE (10ms HARD BUDGET) <<<        |
|                 • 0-2ms: Stream Velocity & Feature Fetch (Flink / Redis)              |
|                 • 2-4ms: Graph Traversal (Device & Card Network Density)              |
|                 • 4-8ms: ML Model Scoring (ONNX C++ Embedded Inference)               |
|                 • 8-10ms: Hard Rules Evaluation & Decision Arbitration                |
|  45ms - 85ms  : Issuing Bank ISO 8583 Authorization Network Hop                       |
|  85ms - 100ms : Response Encryption & Gateway Return Hop                              |

Evaluating transaction velocity (e.g. "Has this card been used more than 4 times in the last 60 seconds across distinct merchant categories?") requires Stateful Stream Processing.

Traditional microservices querying SQL databases with SELECT count(*) WHERE card_id = ? AND timestamp > NOW() - INTERVAL 1 MINUTE fail under load due to index lock contention and database CPU exhaustion.

Apache Flink maintains rolling in-memory aggregation state directly in physical RAM and local SSDs using an embedded RocksDB StateBackend:

sequenceDiagram autonumber participant Card as Payment Stream participant Flink as Flink TaskManager participant RocksDB as Embedded RocksDB State participant Sink as Downstream Scoring Engine Card->>Flink: TxEvent(Card_99, $450.00, t=10:00:01) Flink->>RocksDB: Read Current 60s Sliding Window State for Card_99 Note over Flink: Accumulate count=5, sum=$1,850.00, distinct_ips=3 Flink->>RocksDB: Update RocksDB State Checkpoint Flink->>Sink: Emit Velocity Feature Vector: [count: 5, sum: 1850.00, ip_entropy: 0.92]
  • Event-Time Processing with Watermarks: Handles out-of-order network packets by delaying window closing by a bounded watermark interval ($t_{\text{watermark}} = t_{\text{event}} - \delta$).
  • Keyed Streams: Shards transactions by card_token_hash, ensuring that all transactions for a given card route to the same physical Flink TaskManager core for cache locality.

3. Real-Time Graph Feature Stores: Uncovering Fraud Rings

Organized fraud syndicates use automated bot farms to cycle through thousands of stolen credit card numbers using shared infrastructure:

  • 500 different card numbers sharing 3 distinct browser fingerprints.
  • 100 shipping addresses linked to a single corporate IP proxy.
flowchart TD subgraph SG4_RealTimeBipartite ["Real-Time Bipartite Fraud Graph"] Card1[(Card #101)] --- Device1["Device Fingerprint A"] Card2[(Card #102)] --- Device1 Card3[(Card #103)] --- Device1 Card3 --- IP1["IP Subnet Proxy B"] Card4[(Card #104)] --- IP1 Card5[(Card #105)] --- IP1 end style Device1 fill:#ef4444,stroke:#7f1d1d,color:#ffffff style IP1 fill:#ef4444,stroke:#7f1d1d,color:#ffffff classDef green fill:#dcfce7,stroke:#16a34a,stroke-width:2px,color:#14532d; classDef red fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#7f1d1d; classDef blue fill:#e0f2fe,stroke:#0284c7,stroke-width:2px,color:#0c4a6e; classDef yellow fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#78350f; classDef purple fill:#ede9fe,stroke:#7c3aed,stroke-width:2px,color:#4c1d95; class Device1 blue class IP1 green

Graph Feature Extraction at Sub-2ms Latency:

Instead of querying slow disk-backed relational joins, platforms like Stripe Radar maintain in-memory Bipartite Identity Graphs (Cards $\longleftrightarrow$ Devices $\longleftrightarrow$ IPs $\longleftrightarrow$ Shipping Addresses).

When a transaction arrives:

  1. The engine performs a 2-hop neighborhood expansion in an in-memory graph cache.
  2. It computes graph topological metrics:
    • Node Degree: Count of distinct cards linked to the active device fingerprint ($k_{\text{device}} > 5 ⟹ \text{High Risk}$).
    • Community Density / Fraud Ring Score: Bipartite clustering coefficient indicating coordinated attack behavior.

4. Low-Latency ML Inference: Treelite & ONNX Embedded Runtime

Python machine learning runtimes (e.g. standard scikit-learn or PyTorch via HTTP microservices) introduce $20\text{--}50\text{ms}$ of serialization and network overhead.

Production Solution: Compiled C++ Decision Forests

  • Treelite Compilation: Compiles trained XGBoost / LightGBM gradient-boosted decision trees directly into optimized C/C++ native shared libraries (.so / .dll).
  • Zero Serialization Overhead: Trees are compiled into static CPU branch instructions (if-else assembly trees) evaluated in $< 200 \text{ microseconds}$ directly within the transaction process memory space.

Python Implementation: Real-Time Fraud Stream & Decision Engine

Here is a Python implementation simulating an Apache Flink stateful sliding window accumulator, an in-memory bipartite graph ring detector, and a sub-millisecond fraud decision engine:

import time
from collections import defaultdict, deque
from typing import Dict, List, Set, Tuple

class TransactionEvent:
    def __init__(self, tx_id: str, card_id: str, amount: float, device_id: str, ip_address: str, timestamp: float):
        self.tx_id = tx_id
        self.card_id = card_id
        self.amount = amount
        self.device_id = device_id
        self.ip_address = ip_address
        self.timestamp = timestamp

class StatefulVelocityWindow:
    """
    Simulates Apache Flink 60-second sliding temporal window per card.
    """
    def __init__(self, window_seconds: float = 60.0):
        self.window_seconds = window_seconds
        # card_id -> deque of (timestamp, amount, ip_address)
        self.windows: Dict[str, deque[Tuple[float, float, str]]] = defaultdict(deque)

    def process_event(self, event: TransactionEvent) -> Dict[str, float]:
        card_queue = self.windows[event.card_id]
        
        # Evict expired events outside sliding window
        cutoff = event.timestamp - self.window_seconds
        while card_queue and card_queue[0][0] < cutoff:
            card_queue.popleft()

        # Add current event
        card_queue.append((event.timestamp, event.amount, event.ip_address))

        # Compute real-time window metrics
        tx_count_60s = len(card_queue)
        total_amount_60s = sum(item[1] for item in card_queue)
        distinct_ips_60s = len(set(item[2] for item in card_queue))

        return {
            "velocity_count_60s": float(tx_count_60s),
            "velocity_amount_60s": total_amount_60s,
            "velocity_distinct_ips_60s": float(distinct_ips_60s)
        }

class InMemoryFraudGraphStore:
    """
    Simulates In-Memory Bipartite Graph for detecting carding rings & device farms.
    """
    def __init__(self):
        self.device_to_cards: Dict[str, Set[str]] = defaultdict(set)
        self.ip_to_cards: Dict[str, Set[str]] = defaultdict(set)

    def update_and_query_risk(self, event: TransactionEvent) -> Dict[str, float]:
        self.device_to_cards[event.device_id].add(event.card_id)
        self.ip_to_cards[event.ip_address].add(event.card_id)

        cards_on_device = len(self.device_to_cards[event.device_id])
        cards_on_ip = len(self.ip_to_cards[event.ip_address])

        return {
            "graph_device_card_degree": float(cards_on_device),
            "graph_ip_card_degree": float(cards_on_ip)
        }

class LowLatencyFraudDecisionEngine:
    """
    Evaluates Hard Deterministic Rules + ML Feature Ensembles in < 10ms.
    """
    def __init__(self):
        self.velocity_engine = StatefulVelocityWindow()
        self.graph_store = InMemoryFraudGraphStore()

    def evaluate_transaction(self, event: TransactionEvent) -> Dict:
        start_time = time.perf_counter()

        # 1. Stateful Velocity Feature Extraction (Flink emulation)
        velocity_features = self.velocity_engine.process_event(event)

        # 2. In-Memory Graph Feature Extraction (Graph store)
        graph_features = self.graph_store.update_and_query_risk(event)

        # 3. Deterministic Hard Rules (Zero-Tolerance Gates)
        if velocity_features["velocity_count_60s"] > 5:
            decision = "DECLINE"
            reason = f"Velocity limit breached ({velocity_features['velocity_count_60s']:.0f} tx/60s)"
            risk_score = 99.0
        elif graph_features["graph_device_card_degree"] > 3:
            decision = "DECLINE"
            reason = f"Fraud Syndicate Ring: Device linked to {graph_features['graph_device_card_degree']:.0f} unique cards!"
            risk_score = 95.0
        elif event.amount > 2000.0 or velocity_features["velocity_distinct_ips_60s"] > 2:
            decision = "CHALLENGE_3DS" # Step-up authentication
            reason = "High transaction amount or multi-IP anomaly"
            risk_score = 65.0
        else:
            decision = "APPROVE"
            reason = "Low risk transaction"
            risk_score = 12.0

        latency_ms = (time.perf_counter() - start_time) * 1000.0

        return {
            "tx_id": event.tx_id,
            "decision": decision,
            "risk_score": risk_score,
            "reason": reason,
            "latency_ms": round(latency_ms, 3),
            "features": {**velocity_features, **graph_features}
        }

# Demonstration Execution
if __name__ == "__main__":
    engine = LowLatencyFraudDecisionEngine()
    now = time.time()

    print("🚀 Simulating High-Throughput Payment Authorization Stream...")
    print("=" * 65)

    # 1. Normal Transaction
    tx1 = TransactionEvent("TX_001", "CARD_ALICE", 45.00, "DEV_IPHONE_1", "192.168.1.10", now)
    res1 = engine.evaluate_transaction(tx1)
    print(f" Tx 1 Decision: [{res1['decision']}] | Score: {res1['risk_score']} | Latency: {res1['latency_ms']}ms | Reason: {res1['reason']}")

    # 2. Coordinated Carding Attack (Same device cycling multiple stolen cards)
    print("\n⚡ Simulating Automated Carding Attack on Single Device...")
    for i in range(1, 5):
        tx_attack = TransactionEvent(f"TX_ATTACK_{i}", f"STOLEN_CARD_{i}", 12.50, "DEV_BOT_FARM_88", "10.0.0.99", now + i * 0.5)
        res_attack = engine.evaluate_transaction(tx_attack)
        print(f" Attack Tx {i} Decision: [{res_attack['decision']}] | Score: {res_attack['risk_score']} | Latency: {res_attack['latency_ms']}ms | Reason: {res_attack['reason']}")

Summary: Fraud Architecture Components

Architecture Layer Technology Function Execution Latency
Event Streaming Apache Kafka / Pulsar High-throughput distributed ingestion $1\text{--}2\text{ms}$
Stateful Velocity Apache Flink + RocksDB Rolling sliding window metrics ($60\text{s}, 10\text{m}, 24\text{h}$) $1\text{--}2\text{ms}$
Identity Graph Store In-Memory Graph / Redis 2-hop neighborhood card/device ring detection $1\text{--}3\text{ms}$
Machine Learning Treelite / ONNX C++ Runtime Gradient-boosted decision forest inference $< 0.5\text{ms}$
Rules Arbitration Compiled Native Rules Engine OFAC sanctions, velocity bounds & 3DS challenge triggers $< 0.2\text{ms}$
Total Pipeline SLA Integrated Gateway Engine End-to-end fraud decisioning $\le 10\text{ms}$

Final Architectural Takeaway

Real-time fraud detection at Visa and Stripe scale is the ultimate test of event-driven distributed stream processing and microsecond machine learning inference.

By leveraging Apache Flink stateful windows, in-memory identity graphs, and compiled C++ decision trees, fintech platforms protect billions of dollars in daily transaction volume while preserving seamless, sub-second checkout experiences for global consumers. [2]


Antithesis & Production Trade-Offs: When NOT to Use This Architecture

Even a high-throughput Fraud Detection, Apache Flink architecture introduces non-trivial operational complexity. Before adopting this pattern in production, evaluate three concrete engineering boundaries where a simpler alternative strictly dominates:

  1. Low-Throughput / Single-Node Workloads ($< 50 ext{ req/s}$): If your service operates well below saturation on a single vertical instance and does not face strict multi-tenant tail-latency SLAs (p99 < 50 ms), the bookkeeping overhead of two-tier state partitioning, canonical digest hashing, and sliding-window circuit breakers adds cognitive and operational cost without measurable user-facing gains. A single monolithic transaction boundary with standard database constraints is simpler to debug and operate.
  2. Hard Real-Time Ultra-Low-Latency Paths ($< 50,\mu ext{s}$ Budget): When operating inside kernel-bypass networking, L1/L2 cache-pinned ring buffers, or per-packet hardware data planes, any dynamic heap allocation, cryptographic SHA-256 digest computation, or asynchronous context switch violates the nanosecond/microsecond budget. In those regimes, pre-compute static lookup tables at control-plane configuration time and keep the hot path strictly zero-allocation.
  3. Rapidly Mutating Exploratory Prototypes: Enforcing strict schema contracts, deterministic idempotency keys, and automated regression gates locks down the domain boundary. During week-one exploratory product discovery—where underlying data models change daily and zero real customer capital or tenant PII is at risk—defer heavy verification harnesses until the core workflow contract stabilizes.

References & Further Reading

  1. Carbone, P., et al. (2015). Apache Flink: Stream and Batch Processing in a Single Engine. IEEE Data Engineering Bulletin. https://www.vldb.org/pvldb/vol8/p1970-carbone.pdf ↩︎
  2. Kreps, J., Narkhede, N., & Rao, J. (2011). Kafka: a Distributed Messaging System for Log Processing. NetDB. https://notes.stephenholiday.com/Kafka.pdf ↩︎
  3. Zaharia, M., et al. (2012). Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing. NSDI. https://www.usenix.org/system/files/conference/nsdi12/nsdi12-final138.pdf ↩︎
  4. Axboe, J. (2019). Efficient IO with io_uring. kernel.dk. https://kernel.dk/io_uring.pdf ↩︎
  5. Linux Kernel Community (2024). BPF Documentation. kernel.org. https://docs.kernel.org/bpf/ ↩︎
  6. Høiland-Jørgensen, T., et al. (2018). The eXpress Data Path: Fast Programmable Packet Processing in the Operating System Kernel. CoNEXT. https://dl.acm.org/doi/10.1145/3281411.3281443 ↩︎