How Stripe detects a stolen card in under 100ms
How Stripe's fraud scoring pipeline combines device fingerprinting, transaction velocity checks, geographic anomalies, and a real-time ML model to reject fraudulent charges before authorization.
The Problem Statement
Interviewer: "A user just submitted a charge request to your payment API: card number, amount, merchant ID, and billing address. You have under 100 milliseconds before you must return an accept or decline to the merchant's server. Walk me through how you would design a system that can tell whether this card is stolen."
This question tests three things: your ability to reason under tight latency constraints, your understanding of how ML models get embedded into synchronous request paths, and whether you know the mechanics of real fraud detection beyond "just run a model on it."
Most candidates can describe a fraud model in the abstract. Very few can explain how you extract 150 signals, score them against an ensemble, apply a rule engine, and return a decision in under 100ms at global scale. The 100ms constraint is not decorative. It drives every single architecture decision.
Clarifying the Scenario
The first thing I do in an interview like this is buy myself structured thinking time by clarifying scope before diving in.
You: "Great question. Before I start, I want to make sure I am scoping this correctly. Are we designing the full payment authorization flow, or specifically the fraud scoring component?"
Interviewer: "Focus on fraud scoring. Assume the payment auth itself is a downstream step."
You: "Got it. Is this card-not-present fraud (online transactions), or do I need to cover card-present (POS terminals) as well?"
Interviewer: "Card-not-present. That is where most online fraud happens."
You: "And the 100ms is wall-clock time from the moment the request arrives at Stripe's edge to when the decision is sent back?"
Interviewer: "Yes. That is the constraint."
You: "Perfect. I will structure my answer in four parts: how I partition the 100ms budget, how the feature extraction pipeline works, how the ML model fits into the synchronous path, and what happens in the uncertain middle band where the model is not confident enough to simply allow or block."
My Approach
When I see a real-time scoring problem with a hard latency budget, I always think about it in layers from the outside in.
- The latency budget: What is the deadline, and how do I partition it across all synchronous work that must happen?
- Signal extraction: What features can I compute fast enough to be useful, and which ones need to be pre-computed ahead of time?
- The scoring model: How does an ML model fit into a synchronous hot path without blowing the budget?
- Decision thresholds: What do I do when the model is not confident? This is the interesting part.
- Feedback and drift: How does the system improve over time as fraud patterns change?
The hard insight I arrived at after thinking through this problem is that almost no useful fraud signal can be computed from scratch in 100ms. The entire architecture is built around the principle that you pre-compute everything you possibly can, and at request time you are doing lookups and final assembly, not computation.
The Architecture
Here is the full fraud scoring pipeline as I would draw it at the whiteboard.
Let me walk through the key parts of this system.
Request ingress: When a user submits a payment, Stripe.js (the JavaScript library on the merchant's checkout page) has already collected a device fingerprint and attached it as an encrypted token to the charge request. This token carries browser signals: user agent, screen resolution, timezone, installed font list, canvas fingerprint hash, and WebGL renderer. The collection happens silently before the user clicks "Pay."
Feature extraction: The Feature Extractor has roughly 20ms to assemble up to 150 signals. It does this through parallel cache lookups against four stores: the velocity cache (card charges in the last 2 minutes, 10 minutes, 1 hour; IP charges in the last 5 minutes), the fingerprint store (does this device token match a known-good device for this cardholder), the geo index (where is this IP, and does it match the location of the card's last use), and the merchant profile cache (what risk tier is this merchant, and what threshold applies).
ML scoring: The assembled 150-feature vector is fed into an XGBoost model that is already loaded into the scoring service's heap memory. This is a critical design choice: the model is not called over the network. It runs in-process. Inference on a pre-loaded gradient-boosted tree ensemble typically takes 5-20ms.
Rule engine: The raw ML score passes through a rule engine that applies hard-coded patterns. Examples: "block if this card has a confirmed chargeback in the last 24 hours regardless of score" or "always issue 3DS challenge if the request originates from a Tor exit node." The rule engine can boost or suppress the ML score in either direction.
Decision routing: The final score determines the output. Below 0.3: allow. Above 0.7: block. Between 0.3 and 0.7: issue a 3D Secure challenge. The middle band is where the design gets interesting, and I will cover it fully in Deep Dive 3.
Deep Dive 1: The 100ms Feature Extraction Pipeline
The 100ms constraint is the central design challenge. Let me decompose exactly how that budget gets spent.
The budget is tighter than it looks. 20ms of that 100ms is pure network round-trip (10ms each way for a US-domestic request at the speed of light over fiber). That leaves 80ms of compute time, and I want to maintain a 25ms buffer for GC pauses, cache cold spots, and slow requests.
The 20ms feature extraction window only works if every single data source is sub-millisecond. That means zero database queries during a live request. No synchronous calls to external enrichment services. Every lookup comes from Redis or an in-memory structure pre-loaded into the scoring service itself.
Features that require external enrichment (like whether a BIN represents a prepaid card from a high-risk issuer) must be computed offline and stored in a lookup table that the scoring service loads at startup. The distinction between async-pre-computed features and sync-lookup features is the most important design choice in this entire system.
Here is how I split the feature set:
| Feature | Source | Latency | Sync / Async |
|---|---|---|---|
| Card velocity: charges in last 2 min | Redis counter | sub-1ms | Sync |
| Card velocity: charges in last 10 min, 1 hr | Redis counter | sub-1ms | Sync |
| IP velocity: charges from same IP in 5 min | Redis counter | sub-1ms | Sync |
| Device token match for this cardholder | Redis hash | sub-1ms | Sync |
| Geographic distance from card's last use | In-memory geo index | sub-2ms | Sync |
| BIN risk tier (prepaid, debit, corporate) | In-memory hash map | sub-1ms | Sync (pre-loaded) |
| Merchant risk category | In-memory hash map | sub-1ms | Sync (pre-loaded) |
| Card age: days since first seen by Stripe | Redis sorted set | sub-1ms | Sync |
| Chargeback count on this card in last 90 days | Redis counter | sub-1ms | Sync |
| Implied travel speed from last transaction | Computed from geo + timestamp | ~2ms | Sync |
| Historical spend pattern for this cardholder | Offline ML pipeline | N/A | Async pre-loaded |
| Issuer-level fraud prevalence | Weekly batch job | N/A | Async pre-loaded |
The async vs sync boundary
Any signal that cannot be resolved from a Redis cache or in-process memory within 5ms must be computed offline and stored. The feature extraction step at request time is assembly, not computation. If you design signals that require on-the-fly computation from raw data, you have already blown the latency budget before the model even runs.
Deep Dive 2: ML Model Design and the False Positive Tradeoff
The ML scoring layer is where most of the fraud detection intelligence lives. But the model design is inseparable from how you think about false positives, and that is what most candidates miss.
The model itself is a gradient-boosted decision tree ensemble. Why not a deep neural network? Three reasons. GBDTs inference is faster on tabular data. They are interpretable enough to debug when a surprising score needs investigation. And they handle structured features (counts, ratios, boolean flags, categorical IDs) better than shallow neural networks.
The training data is massively imbalanced. Fraudulent transactions are roughly 0.1% of volume. Without correction, a model that always predicts "not fraud" would be 99.9% accurate and completely useless. The training pipeline applies a class weight adjustment (XGBoost's scale_pos_weight) to penalize false negatives (missed fraud) much more heavily than false positives. The raw model output is a log-odds score. A calibration step using Platt scaling converts this into a well-calibrated probability on [0.0, 1.0].
The false positive problem is the hard part. Every time Stripe blocks a legitimate transaction, a merchant loses a sale. Stripe's published target is around a 0.1% false positive rate on legitimate volume. At hundreds of billions of dollars per year, even 0.1% represents billions of dollars of blocked legitimate commerce. The threshold is not a technical decision. It is a business decision expressed as a number.
Interview tip: name the false positive tradeoff explicitly
Saying "we tune the threshold to balance precision and recall" sounds vague. Instead say: "The threshold is a business tradeoff between chargeback losses and blocked legitimate revenue. Different merchants have different tolerance for each side, so we let merchants configure their threshold within guardrails that Stripe sets."
Deep Dive 3: 3D Secure as a Dynamic Challenge Mechanism
The most interesting part of the design is the middle band (score 0.3 to 0.7). This is where the model is uncertain. Blocking everything in this band creates too many false positives. Allowing everything lets too much fraud through. The answer is to push authentication responsibility to the entity that actually knows the cardholder: the issuing bank. 3D Secure (3DS) is the protocol for doing exactly this.
3DS solves a beautiful problem. When Stripe is uncertain about a transaction, it routes authentication to the entity that has ground truth about the cardholder's identity: the issuing bank. The bank knows whether the cardholder enrolled their phone for push auth, what device they normally use, and whether their current location matches their home country. Stripe leverages the bank's own signals to resolve its uncertainty.
The liability shift is equally important as the fraud signal. In most jurisdictions (Visa and Mastercard network rules), if a transaction passes 3DS authentication and later turns out fraudulent, chargeback liability shifts from the merchant to the issuing bank. The bank chose to authenticate the transaction. If the bank's authentication was compromised, that is the bank's problem. This is a strong incentive for banks to invest in accurate 3DS.
3DS 2.0 and frictionless flows change the user experience dramatically. 3DS 1.0 always showed a visible authentication page. 3DS 2.0 allows the merchant to pass a rich device data payload to the bank's Access Control Server (ACS). The ACS can use this data to authenticate silently without interrupting the user at all. For a middle-band transaction where the device payload matches the cardholder's known device profile, the bank may return a frictionless authentication (ECI=05) in under a second with zero user involvement.
The Tricky Parts
These are the non-obvious challenges that separate a good answer from a great one in this interview.
-
The impossible travel problem: A card used in Paris at 9am is used in Tokyo at 9:30am. Geographic distance divided by elapsed time gives an implied travel speed of approximately 6,000 km/h, far beyond commercial aviation. This is a clear fraud signal, but it requires knowing the precise timestamp and geo coordinates of the last transaction, not just the country. Stripe stores the timestamp and location of every charge per card, and the feature extractor computes implied travel speed on every new charge. This is why geographic feature computation belongs in the sync path: it is arithmetic on pre-stored data, not a new lookup.
-
Velocity window gaming: Fraudsters know about velocity checks. They deliberately space out fraudulent transactions (one per hour instead of five per minute) to evade 2-minute rate limits. The detection approach is to maintain velocity counters at multiple time windows simultaneously: 2-minute, 10-minute, 1-hour, 6-hour, and 24-hour windows. A fraudster spacing charges hourly still shows an elevated 24-hour velocity. All five velocity signals feed into the model as separate features.
-
BIN cluster attacks and card testing: Card testing is where a fraudster holds a list of stolen card numbers and makes tiny test charges across thousands of merchants to identify which cards are still active before using them for large purchases. The card-level signal is clean (each card is used once). The IP-level signal is damning: if IP address X submits 200 distinct card numbers in 15 minutes, that is a bot regardless of per-card velocity. The feature extraction must include IP-level features alongside card-level features, otherwise coordinated attacks sail through individual velocity checks.
-
Model staleness and concept drift: Fraud patterns change faster than most ML training cycles. A new attack vector (like a mass breach at a specific issuer) may not be well-represented in training data from three weeks ago. Stripe mitigates by maintaining the rule engine as a faster-updating layer: rules can be added within hours of a new attack pattern being observed, while the model training cycle catches up over the following weeks. The rule engine is the short-term responder and the ML model is the long-term learner.
-
Cold start on new cards: A brand-new card has no velocity history, no device fingerprint match, and no spend baseline at all. The model falls back entirely to card characteristics (BIN type, issuing country, card account age) and transaction context (merchant category, amount, time of day). First-time uses of new cards are genuinely harder to score, and the false positive rate is meaningfully higher for first transactions. A practical mitigation is to set the 3DS challenge threshold lower for cards with fewer than 3 lifetime transactions in Stripe's network.
What Most People Get Wrong
Here is a table of the mistakes I see most often when candidates answer this question.
| Mistake | What they say | Why it is wrong | What to say instead |
|---|---|---|---|
| Treating scoring as a simple model call | "We run the transaction through our ML model and get a score" | Ignores the entire feature extraction problem and the latency budget | "The hard problem is assembling 150 features in under 20ms, which requires pre-computing everything into Redis and in-process memory" |
| Ignoring false positives | "We use a low threshold to catch as much fraud as possible" | Blocking legitimate transactions costs merchants real money | "The threshold is a business tradeoff. Different merchants have different tolerances, so we allow per-merchant configuration with a floor set by Stripe" |
| Forgetting where labels come from | "The model is trained on labeled fraud data" | Where do labels come from? Chargebacks arrive 30-90 days after the transaction | "Labels come from chargebacks. There is an async pipeline that joins the dispute record back to the original transaction ID and adds it to the training set" |
| Designing a batch model | "We score transactions every 10 minutes in a batch job" | Authorization decisions must be synchronous. A merchant cannot wait 10 minutes | "All scoring is in-process on the synchronous request path. Batch pipelines are for model training only, not inference" |
| Calling the model over the network | "The fraud scoring service calls our ML service via gRPC" | An extra network hop adds 5-20ms. Inference belongs in-process | "The model artifact is loaded into the scoring service's heap at startup. Inference is a local function call, zero network overhead" |
How I Would Communicate This in an Interview
When an interviewer asks this question, they are watching your thinking process as much as your answer content. Here is how I would structure the verbal walkthrough.
First 30 seconds: establish the constraint
I would say: "The central challenge here is the 100ms budget. That constraint drives every architecture decision. Let me decompose that budget first, then build the system around it." This signals immediately that you understand the constraint is the real problem, not just the model.
The diagram moment
I would draw the pipeline: client request, feature extractor (parallel cache lookups), ML inference in-process, rule engine, decision routing. I would label each box with its latency budget. Then I would specifically call out: "The model has to be loaded into the scoring service's heap at startup. If we call the model over the network, we add 5-20ms and eat into our buffer."
The interesting part: the middle band
After describing the happy path (score below 0.3 or above 0.7), I would proactively raise the interesting design question: "What do we do when the model is uncertain, say score between 0.3 and 0.7? This is the part most people skip, and it is actually the most interesting design decision." Then I would introduce 3DS as the challenge mechanism and explain the liability shift. Most candidates never reach this part. If you do, you have already differentiated yourself.
The tradeoff articulation
I would close with: "The threshold is not a technical decision. It is a business decision. Stripe lets merchants configure their own risk tolerance within guardrails. Low-risk established merchants can afford looser thresholds. High-risk merchants like gift card vendors need tighter ones. The fraud scoring system emits a score; what you do with that score is policy, not engineering."
Interview tip: name the feedback loop
The chargeback to training label pipeline is almost never mentioned by candidates. Saying "labels come from chargebacks, and there is an async pipeline that associates the dispute with the original feature vector snapshot so that delayed labels can be used in the next training cycle" immediately separates you from someone who described a generic ML system.
Interview Cheat Sheet
- 100ms budget breakdown: roughly 10ms network in, 5ms parse, 20ms feature extraction, 20ms ML inference, 5ms rule engine, 5ms serialize, 10ms network out. The compute budget is about 55ms with a buffer.
- Zero DB queries on the hot path: every feature lookup is Redis or an in-process hash map. A single synchronous database query during scoring can blow the entire budget on a bad day.
- Features are assembled at request time, not computed: all computation happens offline in batch pipelines. At request time the scoring service does lookups and assembly only.
- Model inference is in-process: the model artifact is loaded into the service's heap at startup. Never call the model via a network hop on the synchronous path.
- Card-not-present is the hard case: no PIN, no physical card. All signals are inferential: device fingerprint, velocity, geo anomaly, behavioral baseline.
- The three-band decision: 0.0-0.3 allow, 0.7-1.0 block, and 0.3-0.7 is the 3D Secure challenge band. The middle band is where the design gets interesting.
- 3DS transfers chargeback liability: a transaction that passes 3DS authentication shifts liability from the merchant to the issuing bank. This is a strong economic incentive embedded in the card network rules.
- 3DS 2.0 enables frictionless flows: by passing rich device data to the bank's ACS, many middle-band transactions authenticate silently with no user interruption. Visible challenges are reserved for truly uncertain cases.
- Per-merchant risk profiles: the same ML score produces different outcomes depending on the merchant's configured threshold, which is derived from their historical chargeback rate and industry.
- Chargeback feedback loop: fraud labels arrive 30-90 days after the transaction via chargebacks. The feature vector must be snapshotted at decision time so delayed labels can be correctly associated during model retraining.
Test Your Understanding
Quick Recap
The 100ms constraint is not a detail, it is the architecture. Every design decision in this system flows from that single constraint.
The feature extraction layer solves the latency problem by pre-computing everything possible offline. At request time, the scoring service assembles features from Redis counters and in-process hash maps. It does not compute anything from raw data during a live request.
The ML model (XGBoost or LightGBM trained on billions of labeled transactions) runs in-process on the pre-assembled feature vector. Inference takes 5-20ms because the model is loaded directly into the service's heap memory at startup. There is no model microservice call.
A rule engine sits above the ML score and applies hard-coded patterns that the model alone might not catch cleanly: confirmed stolen cards, Tor exit nodes, recent chargebacks on this card.
The three-band decision (allow below 0.3, challenge between 0.3 and 0.7, block above 0.7) is the key insight. The middle band routes to 3D Secure, which uses the issuing bank's authentication to resolve the uncertainty and shifts chargeback liability off the merchant.
False positives cost real money at Stripe's scale. Thresholds are business decisions, not technical ones. Per-merchant risk profiles let merchants configure their own tolerance within Stripe's hard-floor guardrails.
The chargeback feedback loop completes the system. Fraud labels arrive 30-90 days after transactions via disputes. The feature vector must be snapshotted at decision time so delayed labels can be correctly joined during the next model retrain cycle.
Related Concepts
-
Rate limiting and velocity checks: Velocity counters in Redis are essentially rate limiters applied to cards and IP addresses. The design patterns for Redis-based rate limiting (sliding window counters, token buckets) overlap directly with fraud velocity feature design.
-
Real-time feature stores: The pre-compute-offline, serve-from-low-latency-store pattern in fraud detection is the foundation of ML feature stores like Feast, Tecton, and Hopsworks. Understanding how Stripe solves feature freshness vs latency gives strong intuition for why feature stores exist.
-
Gradient boosted trees vs neural networks for tabular data: XGBoost and LightGBM dominate fraud detection on structured data. Understanding why GBDTs outperform shallow MLPs on tabular features (feature interactions, handling of sparse inputs, no need for feature scaling) is worth knowing for any ML-in-systems question.
-
3D Secure and EMVCo: The technical standard behind 3DS 2.0 is maintained by EMVCo. The rich data fields (22+ elements) in the 3DS 2.0 authentication request determine whether the bank can perform a frictionless flow. Understanding what data Stripe can send to improve bank authentication accuracy connects to the frictionless conversion optimization problem.
-
Class imbalance in machine learning: Fraud detection is the canonical example of extreme class imbalance (0.1% positive rate). SMOTE,
scale_pos_weight, focal loss, and cost-sensitive learning are all relevant techniques. Any ML-in-production system design question will probe whether you understand this challenge. ASSEMBLY["βοΈ Feature Assembly\n~200 features\nNormalized vector"] MODEL["π GBT Model\nGradient Boosted Trees\nFraud probability 0.0-1.0"] endsubgraph Decision["π Decision Engine (5-10ms)"] RULES["π Rule Engine\nMerchant rules + Hard blocks\nVelocity limits Β· Country blocks"] VERDICT["π Final Verdict\nAllow Β· Block Β· Challenge"] end
subgraph CardNetwork["ποΈ Card Network"] VISA["ποΈ Visa / Mastercard\nAuthorization request\n200-500ms round trip"] end
CHECKOUT -->|"Device fingerprint\n+ card token"| API API -->|"Transaction data"| DEVICE API -->|"Source IP"| GEO API -->|"Card number hash"| VELOCITY API -->|"First 6 digits"| BIN DEVICE -->|"Fingerprint features"| ASSEMBLY GEO -->|"Geo features"| ASSEMBLY VELOCITY -->|"Velocity features"| ASSEMBLY BIN -->|"BIN features"| ASSEMBLY ASSEMBLY -->|"Feature vector"| MODEL MODEL -->|"Fraud score"| RULES RULES -->|"Final decision"| VERDICT VERDICT -->|"If allowed"| VISA VISA -.->|"Auth response"| API
Here is the walkthrough. When a customer submits payment on a merchant's checkout page, Stripe.js has already collected a device fingerprint (browser characteristics, canvas rendering, WebGL hash) silently in the background. The card token and this fingerprint ship to Stripe's API together.
Inside the 100ms window, four feature extraction paths run in parallel: device fingerprint matching (has this device been seen before, and on how many different cards?), IP geolocation with VPN/proxy detection, velocity counter lookups for this card, IP, and device, and a BIN database lookup to determine the issuing bank and country.
These raw features merge into a ~200-dimension feature vector and feed into a gradient-boosted tree model. The model outputs a fraud probability between 0.0 (certainly legitimate) and 1.0 (certainly fraudulent). A rule engine then overlays merchant-specific policies (some merchants block all transactions from certain countries, others set custom velocity limits). The final verdict is allow, block, or trigger a 3D Secure challenge.
For your interview: the key architectural insight is parallelism. You cannot afford to do these lookups sequentially. Four parallel paths, each 30-40ms, still complete in 40ms total because they run concurrently. Sequential execution would blow the budget.
## Feature Extraction Within the Latency Budget
The hardest part of the fraud pipeline is not the ML model itself. It is computing enough features quickly enough to feed the model a useful signal within 30-40ms. This is where most of the engineering complexity lives.
```mermaid
flowchart LR
subgraph Parallel["β‘ Parallel Feature Extraction (max 40ms)"]
direction TB
subgraph Path1["Device Fingerprint Path"]
FP_IN["π Browser fingerprint\nfrom Stripe.js"]
FP_CACHE["β‘ Redis Lookup\nFingerprint β device_id\nTTL: 90 days"]
FP_HIST["β‘ Device History\nCards used: 2\nMerchants: 5\nFirst seen: 60 days ago"]
end
subgraph Path2["Velocity Counter Path"]
VEL_IN["π¨ Card hash +\nIP + device_id"]
VEL_REDIS["β‘ Redis Sorted Sets\nTime-windowed counters\n1min Β· 5min Β· 1hr Β· 24hr"]
VEL_OUT["β‘ Velocity Features\nCard: 3 in 5min\nIP: 12 in 1hr\nDevice: 2 cards in 24hr"]
end
subgraph Path3["Geo + BIN Path"]
GEO_IN["π Source IP"]
GEO_DB["ποΈ MaxMind DB\nIn-memory lookup\n< 1ms"]
BIN_DB["ποΈ BIN Table\nIn-memory lookup\n< 1ms"]
GEO_OUT["π Geo Features\nCountry mismatch\nVPN detected\nDistance from billing"]
end
end
FP_IN --> FP_CACHE
FP_CACHE --> FP_HIST
VEL_IN --> VEL_REDIS
VEL_REDIS --> VEL_OUT
GEO_IN --> GEO_DB
GEO_IN --> BIN_DB
GEO_DB --> GEO_OUT
BIN_DB --> GEO_OUT
I want to call out the specific features that matter most for fraud detection, because this is what interviewers ask about:
Device fingerprint signals: A single device associated with 10+ different credit cards in a week is almost certainly a fraudster testing stolen cards. The fingerprint combines browser user-agent, canvas rendering output, WebGL renderer string, installed fonts, timezone, and screen resolution into a hash. This hash is surprisingly stable, even across browser sessions.
Velocity counters: These are the single highest-signal features. A card that has been charged 5 times in the last 3 minutes is suspicious regardless of everything else. Stripe uses Redis sorted sets with time-windowed counts at 1-minute, 5-minute, 1-hour, and 24-hour granularities. Each window tells a different story.
Geographic anomalies: If the card's billing address is in Tokyo, the IP address is in Nigeria, and the device timezone is set to US Pacific, that is three contradictory geographic signals. The model weighs these heavily.
Card BIN data: The first 6-8 digits of a card number identify the issuing bank and country. Certain BIN ranges have higher fraud rates (prepaid cards, cards from banks with weak fraud detection). This is a static lookup, essentially free in terms of latency.
The most common interview mistake here is describing features without discussing latency. "I would check the user's transaction history for the last 90 days" sounds great, but scanning 90 days of transactions in real time takes too long. Strong answers always pair features with how they are computed fast enough: pre-aggregated counters, in-memory databases, cached snapshots.
Real-Time ML Scoring Pipeline
The ML model is the core of the fraud decision. It takes the ~200-dimension feature vector and outputs a single number: the probability that this transaction is fraudulent. The model architecture, how it is served, and how it is updated all matter.
Here is why gradient-boosted trees (GBTs) win over deep neural networks for this use case. GBTs are fast at inference time (a single prediction takes about 5ms), they handle mixed feature types well (categorical BIN codes, continuous velocity counts, boolean VPN flags), and they produce interpretable feature importances. When a merchant disputes a fraud decision, Stripe can say "this charge was blocked because the device had been associated with 14 different cards in the last week" rather than "the neural network said so."
The model is served in-process, not behind a network call. Stripe loads the model artifact into the same process that handles feature assembly. This eliminates a network round trip (typically 2-5ms each way) and removes a failure mode (model service being down). The tradeoff is that model updates require a rolling restart of the inference fleet, but since models are updated daily (not every minute), this is acceptable.
Balancing False Positives vs Fraud Loss
This is the tradeoff that defines every fraud system. Block too aggressively and you reject legitimate customers (false positives), costing the merchant revenue and destroying the customer experience. Block too loosely and fraudulent charges go through, costing the merchant chargebacks and fees. There is no correct answer, only a correct position on the tradeoff curve.
Here is the core tension. For every 1 fraudulent charge you catch by lowering the threshold, you also block 5-10 legitimate charges. This ratio (called the false positive ratio) is the key metric. Stripe's public numbers suggest their system catches ~95% of fraud while maintaining a false positive rate under 0.5%, meaning fewer than 1 in 200 legitimate charges are incorrectly blocked.
The distinguishing insight in an interview: fraud detection is not a classification problem (fraud vs not-fraud). It is an optimization problem: minimize total dollar loss, where loss = fraud dollars that get through + revenue lost from false positives. The optimal threshold is different for a $3 coffee shop transaction and a $3000 electronics purchase.
3D Secure as a middle ground: When the model is uncertain (score between 0.4 and 0.7), the system does not have to make a binary allow/block decision. It can trigger a 3D Secure challenge, which redirects the customer to their bank's authentication page (SMS code, biometric, etc.). If the real cardholder is making the purchase, they pass easily. If a fraudster is using stolen card details, they usually cannot complete the bank's authentication.
This is the key insight that separates good answers from great ones. Binary allow/block is a false dichotomy. The challenge flow converts uncertain cases into high-confidence decisions without the merchant losing the sale.
Merchant risk profiles: Stripe segments merchants into risk tiers based on industry (digital goods have higher fraud rates than physical goods), historical chargeback rate, average order value, and geography. A new merchant with no history gets the default profile (moderately strict). As data accumulates, the profile loosens or tightens automatically.
The Tricky Parts
-
Label delay: You do not know if a charge was fraudulent until the cardholder notices and files a chargeback, which can take 30-120 days. This means your training data is always 1-4 months behind the current fraud landscape. Fraudsters adapt faster than your labels arrive. The solution is to supplement chargeback labels with early signals: dispute filings, merchant manual reviews, and card network fraud alerts that arrive within 24-48 hours.
-
Adversarial adaptation: Fraudsters are not static. When Stripe's model learns to catch velocity-based attacks (rapid-fire charges on a stolen card), fraudsters switch to low-and-slow attacks (one charge per day, spread across many merchants). The model must be retrained continuously, and the feature set must evolve. This is an arms race, not a one-time model deployment.
-
Network effects create privacy tension: Stripe's biggest advantage is cross-merchant data. A card flagged as fraudulent on Merchant A protects Merchant B immediately. But this means Stripe is sharing (abstractly) transaction patterns across merchants, which creates regulatory and privacy questions. The architecture must extract features without exposing raw transaction details across merchant boundaries.
-
Cold start for new merchants: A brand-new merchant on Stripe has no transaction history, no chargeback rate, no fraud profile. The system defaults to the industry average, but this is often wrong. A legitimate digital goods merchant might get overly strict defaults because their industry has high fraud rates. The system needs to recalibrate quickly as real data arrives.
-
Card testing attacks: Fraudsters often "test" stolen cards with tiny charges ($0.50 or $1.00) to see which cards are live before making a large purchase. These micro-charges look legitimate individually, but the pattern (50 small charges from the same device across 50 different cards in 10 minutes) is unmistakable. The velocity counters must track per-device patterns, not just per-card patterns.
What Most People Get Wrong
| Mistake | What they say | Why it is wrong | What to say instead |
|---|---|---|---|
| Rule engine only | "I would build a set of rules: block if velocity > 5/min" | Rules catch known patterns but miss novel fraud. Fraudsters learn the rules. | "Rules as a safety net, ML model as the primary scorer. Rules catch the obvious stuff, the model catches evolving patterns." |
| Ignoring latency | "I would check the user's full transaction history" | Scanning months of history in real time takes too long. The 100ms budget is real. | "Pre-aggregated velocity counters in Redis. I get the last 1-min, 5-min, 1-hr counts in a single O(1) lookup." |
| Binary decision | "Block if fraud score > 0.7, allow otherwise" | Misses the 3D Secure middle ground. Also, one threshold for all merchants is wrong. | "Three outcomes: allow, challenge with 3D Secure, or block. Threshold varies by merchant risk profile and transaction value." |
| Ignoring false positives | "I would set a very strict threshold to catch all fraud" | Blocking 10% of legitimate charges would destroy merchant revenue. False positives are expensive. | "I optimize for total dollar loss: fraud that gets through plus revenue lost from false positives. The threshold is an economic decision." |
| No feedback loop | "Train the model once on historical data" | Fraud patterns evolve weekly. A static model degrades fast. | "Daily model retraining with new chargeback labels, merchant reviews, and card network alerts as ground truth." |
How I Would Communicate This in an Interview
Here is how I would actually say this in 90 seconds:
"Stripe's fraud detection runs in a pre-authorization window of about 100ms. When a charge comes in, four feature extraction paths run in parallel: device fingerprinting from Stripe.js (has this browser been seen on other stolen cards?), IP geolocation with VPN detection, velocity counters from Redis (how many charges on this card in the last 5 minutes?), and a BIN lookup for the card's issuing bank and country.
These features, about 200 dimensions total, feed into a gradient-boosted tree model that outputs a fraud probability between 0 and 1. GBTs are used instead of neural networks because they are fast at inference time (about 5ms), handle mixed feature types well, and produce interpretable feature importances that merchants and regulators can understand.
The ML score then passes through a rule engine with merchant-specific policies. The final decision is not binary. Allow, block, or challenge with 3D Secure. For uncertain cases, a 3D Secure challenge redirects to the bank's authentication (SMS code or biometric), which converts an uncertain case into a high-confidence decision without losing the sale.
The tricky part is latency. You cannot compute every feature you want in 100ms. The architecture is shaped by what is fast enough to compute, not what would be most predictive with unlimited time. Velocity counters are pre-aggregated in Redis. Geolocation is an in-memory database lookup. BIN data is a static table. Nothing requires a database scan or API call outside the Stripe network."
Interview Cheat Sheet
- Trigger: "How does fraud detection work in real time?" β "Four parallel feature paths (device, geo, velocity, BIN) feeding a gradient-boosted model, all within a 100ms pre-auth budget."
- Trigger: "Why not a rule engine?" β "Rules catch known fraud patterns. ML catches evolving ones. You need both: rules as the safety net, model as the adaptive layer."
- Trigger: "Why gradient-boosted trees over neural networks?" β "Faster inference (~5ms vs ~50ms), handles mixed feature types natively, and outputs interpretable feature importances for compliance and merchant explanations."
- Trigger: "How do you handle the 100ms constraint?" β "Parallel feature extraction, in-memory lookups (no cross-network calls), pre-aggregated velocity counters in Redis, and in-process model serving (no model-service network hop)."
- Trigger: "What about false positives?" β "Fraud detection is an optimization problem, not a classification problem. Minimize total dollar loss: fraud cost plus lost revenue from false positives. 3D Secure is the middle ground for uncertain cases."
- Trigger: "How does the model stay current?" β "Daily retraining with chargeback labels, merchant manual reviews, and card network fraud alerts. Shadow scoring and canary deployment for new model versions."
- Trigger: "What is the single highest-signal feature?" β "Velocity counters. A card charged 5 times in 3 minutes is suspicious regardless of everything else. Per-device velocity is even more telling: 8 different cards from the same browser fingerprint in 24 hours."
- Trigger: "How does card testing work?" β "Fraudsters test stolen cards with micro-charges ($0.50) before making large purchases. Per-device velocity counters catch this: 50 small charges from one browser across 50 cards in 10 minutes."
- Trigger: "What about new merchants?" β "Default to industry-average risk profile, then recalibrate as real transaction data accumulates. Overly strict defaults protect Stripe but hurt merchant conversion, so fast recalibration matters."
- Trigger: "How does cross-merchant data help?" β "A card flagged on Merchant A protects Merchant B immediately. This network effect is Stripe's competitive advantage over merchant-local fraud systems."
Test Your Understanding
Quick Recap
- Stripe's fraud detection runs in a pre-authorization window of approximately 100ms, well before the charge reaches the card network.
- Four feature extraction paths run in parallel: device fingerprinting, IP geolocation, velocity counters (the highest-signal feature), and card BIN data.
- A gradient-boosted tree model scores the feature vector, chosen over neural networks for speed, mixed-type handling, and interpretability.
- The decision is not binary. Allow, block, or trigger 3D Secure challenge. This three-outcome framework is what separates production systems from textbook classifiers.
- Thresholds are calibrated per merchant based on industry, chargeback rate, and average transaction value, not set globally.
- The 100ms budget forces architectural choices: in-memory lookups, pre-aggregated counters, in-process model serving, and parallel execution.
- Continuous retraining (daily) is essential because fraudsters adapt their patterns faster than chargeback labels arrive.
- Cross-merchant network effects are Stripe's competitive moat. A stolen card detected on one merchant protects every other merchant on the platform immediately.
Related Concepts
- Rate Limiting and Throttling: The velocity counter system uses the same underlying data structures (sliding windows, sorted sets) as API rate limiters, just applied to fraud signals instead of request counts.
- Feature Stores for Real-Time ML: The parallel feature extraction architecture mirrors the feature store pattern used in recommendation systems and search ranking, where pre-computed features are served with low latency.
- Circuit Breaker Pattern: The graceful degradation when Redis is unavailable (fall back to cached values, then to reduced-feature model) follows the circuit breaker pattern applied to ML inference pipelines.
- Event Sourcing and CQRS: Velocity counters are essentially a materialized view of the transaction event stream, optimized for fast reads. The write path (logging transactions) is separated from the read path (querying velocity counts).
- A/B Testing and Canary Deployments: New fraud models are deployed with shadow scoring (scoring in parallel with the production model but not acting on the result) before canary deployment (routing a small percentage of traffic to the new model).