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 scenario
A cardholder may be legitimate, a fraudster may have stolen card details, or a genuine customer may look unusual because they are traveling. A payment decision must happen quickly, protect the merchant, and avoid rejecting too many good customers.
Fraud detection is therefore a risk decision under uncertainty. It combines payment, account, device, network, and historical signals; chooses among allow, challenge, review, and decline; and keeps learning from later outcomes.
30-second mental model
Build a low-latency feature vector, score it with a calibrated model and deterministic rules, and map the result to an action with an explicit cost for false positives and false negatives. Strong evidence can decline; ambiguous evidence can trigger an issuer challenge such as 3-D Secure; low-risk traffic can proceed. The decision is revisable when disputes, reports, or authentication results arrive later.
This article presents a reference payment-fraud architecture inspired by common processor capabilities. Stripeβs public APIs and 3-D Secure flow are relevant product context, but private model features, thresholds, model family, and infrastructure numbers should not be inferred.
5-minute end-to-end flow
- Receive a payment request and bind it to an authenticated customer, merchant, amount, currency, and attempt ID.
- Assemble cheap cached signals in parallel with request-time signals such as device, network, velocity, and payment-instrument context.
- Apply hard rules and a calibrated risk model; record the feature/model version used for the decision.
- Allow, decline, hold for review, or request an issuer challenge; keep the customer-facing response generic enough not to reveal defensive rules.
- Persist the decision and emit an auditable event; process authentication outcomes, disputes, refunds, and analyst labels asynchronously.
- Monitor approval, fraud, dispute, challenge, and false-positive rates by segment, and roll models back when calibration or latency degrades.
The Architecture
Here is the full fraud scoring pipeline as I would draw it at the whiteboard.
The following walk-through covers 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 engineers 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.
design review 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.
Bottlenecks, failure modes, and operations
These are the non-obvious challenges that separate a good answer from a great one in this design review.
-
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.
Common mistakes and misconceptions
Here is a table of the mistakes that come up most often when engineers 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" |
Practical checklist
- Separate deterministic rules, cached reputation, request-time signals, and model inference so each has a clear failure mode.
- Bind risk features to the payment attempt and record the model/rule version used for every decision.
- Optimize for calibrated decisions by segment, not a single global threshold or accuracy number.
- Use allow, challenge, review, and decline outcomes with explicit business costs for false positives and fraud losses.
- Keep challenge flows resumable and idempotent; authentication success is evidence, not a guarantee against every fraud type.
- Feed disputes, reports, appeals, and analyst labels back into training only after checking label quality and leakage.
- Monitor approval, fraud, dispute, challenge, latency, calibration, and subgroup false-positive rates.
- Minimize sensitive data, enforce retention/access controls, and make every decision explainable enough for support and audit.
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).