Design real-time fraud detection for payments
The interviewer asks
“Design a fraud detection system for a payments company that scores every transaction in real time, before it is approved, and decides to allow, challenge or block it. It should catch new fraud patterns quickly and keep false declines low.”
What a strong answer looks like
A strong answer puts a risk scoring service inline in the payment authorisation path with a strict latency budget (tens of milliseconds) and a defined fallback when it is slow. The score combines a rules engine (fast to change, explainable, used for known patterns and regulatory blocks) with an ML model (usually gradient-boosted trees) that reads features from an online feature store. Most of the work is in features: velocity counters over sliding windows, device and network signals, user and merchant profiles, and graph links, computed by a stream processor and served in a few milliseconds. Decisions are logged with everything needed to explain and replay them, labels (chargebacks, user reports, confirmed fraud) flow back weeks later, and new models go through shadow mode and champion-challenger. The hard parts are the latency budget, training/serving feature consistency, delayed and biased labels, and balancing fraud loss against false declines.
Concepts to mention
1 Requirements
Functional
- Score every payment (card, UPI, wallet) before authorisation and return allow, challenge (step-up: OTP, 3-D Secure, re-enter PIN) or block, with reason codes
- Rules engine that analysts can update without a deploy (block lists, velocity limits, merchant-specific rules)
- ML model scoring with features about user, device, merchant, payment instrument and network
- Case management: queue suspicious transactions for analysts, record their decisions
- Ingest labels: chargebacks, user fraud reports, analyst outcomes; use them for training and monitoring
- Shadow mode and A/B testing for new rules and models
Non-functional
- Scoring latency p99 under 50 ms inside a payment that has a total budget of a few hundred ms
- Throughput: ~10k transactions/s average, ~50k/s peak (festive sale, salary day)
- Availability 99.99%; if scoring fails or times out, the payment still gets a decision from a fallback policy
- Fresh features: a transaction must be visible in velocity counters within ~1 s
- Every decision explainable and replayable for audits and disputes
- Rule changes live within minutes; model changes within a day, with safe rollback
Out of scope (say it out loud)
- Merchant onboarding risk (KYB) and credit risk
- Anti-money-laundering transaction monitoring (batch, regulatory reporting)
- Chargeback dispute representment workflow
- Payment processing itself
2 Estimates
3 API
| Endpoint | What it does |
|---|---|
gRPCRisk.Score | Inline: transaction (amount, instrument, payer, payee, merchant, device, IP, location) → decision, score, reasons, decision_id. Deadline 50 ms |
Kafkapayments.events | Authorisation results and payment outcomes, consumed by the feature pipeline |
Kafkarisk.labels | Chargebacks, user reports, analyst verdicts keyed by transaction id |
POST/rules | Create or update a rule (condition, action, priority, mode: shadow or active), versioned with approval |
GET/cases?status=open | Analyst queue with evidence: transaction, linked entities, rule hits, model explanation |
POST/cases/{id}/verdict | Analyst marks fraud or legitimate; becomes a label and may update block lists |
4 Data model
online_featuresRedis Cluster / Aerospike (in-memory, replicated)key = entity_type:entity_id → hash of features (txn_count_1m, txn_count_1h, amount_sum_24h, distinct_payees_24h, first_seen, avg_amount_30d, device_count)Single-digit ms reads with pipelining. Partition by entity key. TTLs per window to bound memory.
window_stateFlink keyed state (RocksDB) with checkpoints to S3entity keysliding window buckets (for example 60 one-minute buckets for 1h)Stream job computes counters and pushes results to the online store; checkpoints let it recover without recounting.
decision_logKafka → S3 (Parquet/Iceberg)decision_idtxn_idtsfeature_vectormodel_versionmodel_scorerules_versionrule_hitsdecisionlatency_msExact features used at decision time; used for audit, replay and training (point-in-time correct by construction).
labelsPostgres + S3txn_idlabel (fraud, legit, unknown)source (chargeback, report, analyst)reason_codelabel_timeJoined to decision_log for training; label_time lets you build training sets that only use labels known at a given date.
rulesPostgres, cached in scoring service memoryrule_idversionexpressionactionprioritymodeownercreated_atScoring nodes reload a compiled rule set on change (pub/sub notification) with a version number in every decision.
entity_graphGraph store (Neo4j / JanusGraph) or adjacency tablesnodes: userdeviceVPAcardbank accountIP; edges with first_seencountOffline and near-real-time graph features (cluster size, links to known fraud) are precomputed and written to the online store; no graph traversal in the hot path.
5 Architecture
Hover a box to highlight its connections.
6 Request flows
Score a UPI payment
- 1User pays ₹18,000 to a new VPA at 1:40 AM from a device first seen 2 hours ago. Authorisation service calls Risk.Score with a 50 ms deadline.
- 2Risk service runs hard rules first in memory (blocked VPA, blocked device, sanctioned entity): no hit.
- 3It reads features for payer, device, payee VPA, IP and merchant from the feature store in one pipelined batch (~3-5 ms): payer's first transfer to this payee, device age 2 h, 4 transfers in the last 10 minutes, payee received money from 37 new payers today.
- 4Model server scores the feature vector (~2-5 ms): probability of fraud 0.83.
- 5Decision policy combines score, amount and rules: above the block threshold for new-device high-value transfers → block, with reasons 'new device', 'payee velocity'. Lower scores might get a challenge instead.
- 6Risk returns the decision in ~20 ms total; the decision log record goes to Kafka asynchronously.
- 7The payment event updates velocity counters in Flink within a second, so the next attempt from this device sees 5 attempts, not 4.
- 8Because the payee looks like a mule account, a case is opened for analysts and the payee's risk profile is updated.
Label feedback and retraining
- 1Chargebacks and user fraud reports arrive days or weeks later and are written to the labels topic with the original txn_id.
- 2Nightly jobs join labels to the decision log (which already holds the exact features used), building a point-in-time correct training set.
- 3A new model is trained and evaluated on a recent time-based holdout (not a random split), focusing on precision at the operating threshold and money saved.
- 4The new model runs in shadow mode for a week (scores logged, not used), then serves 5-10% of traffic as challenger before full rollout.
7 Deep dives
The latency budget and the fallback
Fraud scoring sits inside authorisation, so every millisecond is paid by every payment. A practical budget at p99: 5 ms network and parsing, 10 ms feature reads, 5 ms model inference, 2 ms rules, leaving headroom inside a 50 ms deadline. To hit it: read all features in one pipelined batch (or one multi-get per shard) instead of 20 sequential calls; keep the rules and model in memory on the scoring nodes or in a sidecar to avoid an extra hop; use tree models that infer in microseconds to a few milliseconds on CPU; avoid any synchronous graph traversal or database query. Then decide the timeout behaviour explicitly, because a scoring outage will happen. Fail-closed (block when unsure) stops fraud but turns an outage into a payments outage. Fail-open (allow everything) keeps payments flowing but invites attackers who notice. The usual answer is a degraded policy: if the model or feature store times out, apply a simple local rule set (amount caps for new devices, block list from local cache) and allow the rest, and alert immediately. Hedged requests to a second replica after p95 latency also cut tail latency. Interviewers want to hear the budget broken down and the fallback chosen deliberately.
Real-time velocity features
Most fraud signal is 'how unusual is this compared to recent behaviour': how many payments from this device in 10 minutes, how many new payers sent money to this VPA today, total amount from this card in 24 hours. These are sliding-window aggregates over entity keys. Compute them in a stream processor (Flink) keyed by entity, with windows built from small buckets (for example 60 one-minute buckets for a 1-hour window), and push the current values to the online store after each event. An alternative used by many teams is to update counters directly in Redis on the authorisation path (INCR on time-bucketed keys with TTL), which gives the freshest data but adds writes to the hot path; a hybrid keeps a few critical counters inline and the rest in Flink. Freshness matters: attackers run bursts of attempts within seconds, so a feature that lags a minute misses the attack; aim for under a second. Use the same feature definitions for training: logging the exact feature vector at decision time into the decision log is the simplest way to avoid training/serving skew, because training then uses exactly what the model saw. Watch out for hot keys: a big merchant's counters get updated thousands of times per second; shard the counter and sum on read.
Rules plus model, and the decision policy
Rules and models do different jobs. Rules are for known, crisp patterns and for reacting within minutes to a new attack ('block payments over ₹5,000 to VPAs created in the last 24 hours from this bank'), for regulatory requirements, and for allow lists. They are readable and fast to change but brittle and hard to maintain once there are thousands. The model (gradient-boosted trees such as XGBoost or LightGBM) generalises across hundreds of weak signals and is robust to small changes. Combine them in a decision policy layer: hard block rules first, then the model score, then thresholds that depend on context (amount, merchant category, user tenure), mapped to allow, challenge or block. Challenge (OTP, 3-D Secure, PIN re-entry, cooling-off for first-time large transfers) is valuable because it stops many fraudsters while costing honest users only a little friction. Every rule should run in shadow mode before going active, and rules need owners and expiry dates, or the rule set rots. Measure both sides: fraud basis points caught and false positive rate (good users declined or challenged), with the money impact of each, because a false decline often loses a customer, not just a transaction.
Labels: delayed, partial and biased
Fraud labels are hard. Chargebacks can arrive weeks after the payment; for UPI, fraud often shows up as user complaints and bank reports rather than chargebacks. Many frauds are never reported, so 'not labelled' does not mean 'legitimate'. Train on transactions old enough that most labels have arrived (for example 60-90 days), and use the time when a label became known so the training set only contains information available at that date. There is also selection bias: transactions you blocked never produce outcome labels, so the model learns only from what earlier systems let through. Counter this by letting a tiny random sample of would-be-blocked, low-amount transactions go through with step-up authentication (exploration), or by using analyst review outcomes on blocked cases. Evaluate on a time-based holdout because fraud patterns drift. Monitor in production: score distribution, block rate, challenge pass rate and feature drift per day; a sudden change in block rate usually means a broken feature, not a fraud wave. Feed analyst verdicts back quickly as fast labels, and use them to retrain more often for the patterns attackers change fastest.
8 Trade-offs
Inline synchronous scoring vs async post-authorisation detection
Inline scoring can stop fraud before money moves, which is essential for UPI where transfers are instant and hard to reverse. It adds latency and becomes part of the payment's availability. Async detection after authorisation is cheaper and can use heavier models and graph analysis, but can only catch fraud after the fact (useful for blocking accounts and mule detection). Use both: inline for decisions, async for deeper analysis that updates features and block lists.
Stream-computed features vs inline counters
Flink-computed features keep the auth path read-only and support complex windows, but have a small lag. Inline Redis counters are freshest but add writes and failure modes to payments. Most teams do a hybrid with a few inline counters for burst attacks.
Gradient-boosted trees vs deep learning
Trees are fast on CPU, strong on tabular features and easy to explain with SHAP. Deep models (sequence models over a user's history, graph neural networks) can catch subtler patterns but cost more to serve and explain. Start with trees; add embeddings from deep models as features computed offline.
9 Bottlenecks
Feature store reads at 10x
At 500k transactions/s, feature reads reach ~10M keys/s. Co-locate features for an entity in one hash so one read fetches many features, keep a small in-process cache for slow-changing profiles (merchant risk, user tenure), and scale the in-memory store horizontally by entity key.
Hot merchant or hot payee keys
A large merchant during a sale produces thousands of updates per second on the same counter. Shard counters into N sub-keys and sum on read, or have Flink pre-aggregate per second before writing.
10 Failure handling
Feature store unavailable
Scoring falls back to the degraded policy (local rules and cached block lists), payments continue with tighter limits for risky segments, and on-call is paged. Features rebuild from Flink checkpoints and Kafka replay when it recovers.
Bad model or broken feature in production
Block rate jumps from 0.3% to 4% within minutes. Automated guards compare live decision rates with the expected range and roll back to the previous model version or switch the broken feature to a default value. All models are versioned, and rollback is a config change.
Flink pipeline lag
Velocity features go stale, and burst attacks may slip through. Alert on consumer lag in seconds; while lag is high, the scoring service tightens thresholds for new devices and high amounts until features are fresh again.
11 What impresses
- Breaking the 50 ms budget into feature read, inference and rules, and saying what happens on timeout
- Logging the exact feature vector per decision so training data matches serving
- Explaining label delay and selection bias and how to handle them
- Using challenge as a middle option and measuring false declines, not only fraud caught
- Precomputing graph features offline instead of traversing a graph in the hot path
- Monitoring block rate as a health signal to catch broken features fast
12 Mistakes
- Querying the transactional database for velocity counts on every payment
- Only rules, or only a model, with no way to react to new attacks in minutes
- Training on randomly split data, leaking future information
- Ignoring what happens when the fraud service is down
- Treating unlabelled transactions as legitimate without thought
- Optimising accuracy on a 0.05% fraud rate (a model that says 'never fraud' is 99.95% accurate)
13 Follow-ups
Lessons this design uses
Practice questions
- Fraud losses are rising on your wallet app. Risk wants a fraud check on every payment, but the payment API must stay under 300 ms p99. How would you add it?
- A payments app sees accounts taken over via SIM-swap and social engineering, followed by quick money transfers. Design a risk-based authentication system that stops this without annoying normal users.
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.