Skip to content
Engineering10 min read

Architecting a Real-Time Fraud and Risk-Scoring Pipeline

How to score every transaction in-line under a hard latency budget, without sacrificing feature freshness, auditability, or the ability to retrain safely.

  • Kafka
  • Flink
  • Feature store
  • gRPC
  • Redis
  • Gradient-boosted trees
  • Rules engine
  • Event sourcing

By TRAGenX Engineering

The problem and its constraints

A real-time fraud pipeline sits directly in the authorization path of a payment or account action, which means it inherits the latency budget of whatever it's attached to. If card authorization has to respond within a couple hundred milliseconds end to end, the scoring call is one of several hops sharing that budget — network, ledger checks, issuer round-trip — so the model and its feature lookups typically get a small slice of it, not the whole thing. That constraint shapes almost every downstream decision: you cannot join across five services synchronously, you cannot run a large ensemble that takes tens of milliseconds per call at the tail, and you cannot wait on anything that might itself be waiting on a queue.

The second constraint is that fraud is adversarial and non-stationary. Static rules decay because fraud rings adapt to them within days. Pure ML models decay because the label you're training against — a chargeback or a confirmed fraud report — often arrives days or weeks after the transaction, so the model is always learning from a stale snapshot of attacker behavior. The architecture has to accept that no single model or ruleset is the source of truth forever; it has to be built to be replaced.

The third constraint is asymmetry of cost. A false positive blocks a legitimate customer and generates support load and reputational cost; a false negative is a loss event, and in a regulated payments context it can also be a compliance failure if it's part of a pattern the institution should have caught. The pipeline has to expose a tunable operating point rather than a single binary answer, because the right trade-off differs by merchant category, transaction size, and customer tenure, and it changes over time as the institution's risk appetite changes.

Feature architecture: the part that actually determines accuracy

The single highest-leverage piece of this system is not the model — it's the feature store, specifically the discipline of guaranteeing that the features computed at serving time are identical, bit for bit, to the features the model saw in training. This is the classic train/serve skew problem, and in a fraud system it's more dangerous than in most ML applications because the skew is often invisible until a specific fraud pattern slips through in production despite looking well-caught in offline evaluation.

The practical answer is a feature store with two write paths into the same feature definitions: a batch path that backfills historical features for training from the event log, and a streaming path that maintains the same features online, low-latency, and read-consistent at inference time. Point-in-time correctness matters here — when you materialize a training example for a transaction at time T, every feature has to reflect only what was known before T, not what the batch job happened to compute after the fact. Getting this wrong is the most common way these systems quietly overfit.

Velocity and aggregation features — transaction count and volume over the last N minutes/hours per card, per device, per IP, per merchant — are usually the strongest predictive signal, and they're also the hardest to serve cheaply. A stream processor (Flink or an equivalent windowed-aggregation engine) consumes the transaction event log, maintains sliding-window counters keyed by entity, and pushes current values into a low-latency store like Redis that the scoring service reads synchronously. The event log itself should be the append-only source of truth — Kafka topics partitioned by entity key — so that both the online aggregator and the offline feature backfill derive from the same immutable record, rather than two systems each keeping their own interpretation of history.

The inline decision path

At transaction time, the scoring service does three things inside its latency budget: pull precomputed features from the online store (a handful of Redis or equivalent lookups, parallelized), run the model via a low-overhead serving layer (gRPC to a colocated model server, not a REST hop to a shared cluster), and apply a rules layer on top of the model's score. The rules layer is not vestigial — it's where compliance-mandated hard blocks live (sanctioned entities, velocity caps that must never be exceeded regardless of what the model says), and where you encode fresh knowledge about an active fraud pattern faster than a retrain cycle allows. A model score plus a rules layer, combined into a final decision with clear precedence (hard block > model + soft rules > allow), is more operable than either alone.

What happens after the inline decision matters as much as the decision itself. Every scored transaction, its features, its score, and the eventual outcome should be written to an audit log that's queryable independently of the hot path — both because regulators will ask why a specific transaction was blocked or allowed, and because that log is the raw material for the next model iteration. This write should be asynchronous and off the critical path; the authorization decision must never wait on audit persistence.

A separate, non-blocking async path re-scores transactions after the fact with a heavier model or an ensemble that wouldn't fit the synchronous budget, and can raise a case for human review even after the transaction has already been approved. This two-speed design — a cheap model in-line, a more expensive model downstream — lets you use latency-constrained inference where it's mandatory and unconstrained inference where it isn't, instead of forcing one model to serve both purposes.

Correctness, failure handling, and the fail-open question

The scoring service will fail sometimes — a feature store timeout, a model server that's mid-deploy, a network partition. The design decision that matters most here is what the caller does when scoring doesn't return in time: fail open (allow the transaction, treat it as unscored) or fail closed (block it). Fail-closed sounds safer but at any real transaction volume it turns an infrastructure blip into a customer-facing outage, and payment rails generally treat availability as a hard requirement independent of fraud risk. The usual answer is a bounded fail-open with compensating controls: if scoring times out, fall back to a cheap, always-available rules-only decision (which is deliberately more conservative than the ML path), log the fallback explicitly, and flag the transaction for post-hoc review. The full outage mode — no model, no rules, no fallback — should simply not be reachable by design.

Idempotency is the other correctness requirement that's easy to get wrong. Payment events retry, and a scoring request that gets replayed must not double-count itself into the velocity features or generate two contradictory decisions for one transaction. Keying every request by a stable idempotency key, and making the feature-aggregation writes idempotent against that key rather than purely additive, avoids a class of bugs that otherwise only shows up under load or during retries — exactly when you can least afford it.

Model deployment itself needs the same rigor as the transaction path. New models go out in shadow mode first — scored alongside the production model, logged, never affecting a real decision — then in a champion/challenger split on a small percentage of traffic, with drift and calibration monitored before a full cutover. A model that scores well offline but was trained on a feature distribution that has since shifted is a common and quiet failure mode; shadow scoring against live traffic is the cheapest way to catch it before it reaches a customer.

Trade-offs and what we'd deliberately avoid

We would not put a large ensemble or a deep model with unpredictable tail latency directly in the synchronous authorization path — the two-speed architecture exists precisely so the heaviest modeling work happens off that path. We would also avoid building bespoke per-merchant or per-market models as a first move; a shared model with rich contextual features (merchant category, geography, tenure) generalizes better and is far easier to operate than a fleet of narrow models that all need independent monitoring, retraining, and drift checks.

We'd resist the temptation to let the rules layer sprawl. Rules are fast to write and fast to accumulate, and a rules engine with hundreds of overlapping conditions becomes unauditable and starts fighting the model's own signal. Rules should be reserved for cases with clear, defensible logic — regulatory blocks, known bad actors, hard velocity ceilings — with everything else left to the model, which can be evaluated, retrained, and explained systematically in a way an ad hoc rule pile cannot.

Finally, we'd treat the label-lag problem as a first-class architectural concern rather than a data-science afterthought. Because confirmed fraud labels arrive late, any system that assumes a fast feedback loop will systematically underestimate its own blind spots. Building the case-management and outcome-labeling workflow into the pipeline from day one — rather than bolting it on once a model is already in production — is what makes the retraining loop trustworthy instead of aspirational.

Building something like this?

This is the kind of foundational systems work TRAGenX takes on. If you have a project that needs a core other things can trust, tell us about it and we'll get back to you.