Catch the ring.
Clear the rest.

A layered AI filter for mule accounts and scam money flows. A small gatekeeper model clears most payments; the rest are scored by gradient boosted trees and linked into ring cases, with reasons. Built and benchmarked on synthetic datasets.

How it works

We've cracked the cascade.

Each layer approves what it is confident is clean and passes the rest to a heavier model, so heavier models only see about one payment in five. Figures below are committed benchmark results on synthetic data.

L-1 · Lure detector (new)

Catch the scam before the payment

Scores the SMS, WhatsApp message or call transcript that starts the scam, names the workflow (digital arrest, KYC block, task job…) and warns once. A payment to a new payee within 72 h gets a cool off period.

0.95precision of push warnings on unseen Indian scam templates (synthetic) · offline CV
L0 · Onboarding gate

Stop mule accounts at opening

LightGBM scores each new application, now trained on BAF Base and all five bias variants. Separate thresholds per age group keep false alarms even across customers.

0.93false alarm parity, 50+ vs under 50, on Base test months (0.88 before the variants were added)
L1 · Gatekeeper

Clear clean traffic instantly

An XGBoost model of 30 trees reads each account's history (fan-out, pass-through, first time payees) and approves the obvious.

0.3 msper payment; kept 99.2% of laundering for the next layer · reported
L2 · Graph boost (live)

Score what's left, with reasons

The live API averages an XGBoost and a LightGBM model. Reasons come from XGBoost's per feature contributions. Random Forest and LSTM/Transformer blends exist only in the offline benchmark.

0.711PR-AUC of the live path on all HI-Small test weeks, now trained on HI-Small + LI-Small (0.345 on LI-Small)
Context layer · ctx-1.4

Read the message with the money

With consent, the scam message is redacted and scored for cues, then fused with new payee, amount and mule signature signals. A high band needs two domains; context alone can only hold a payment for an analyst.

12/12design scenarios reproduced by the live API · synthetic
L4 · Ring linker and review desk

Turn alerts into ring cases

Alerts that share accounts are merged into one case per mule cluster, so analysts review rings, not rows. A block needs two different analysts, and every action lands in a hash chained audit log.

3.5×fewer items to review (1,105 alerts → 318 cases) · offline benchmark
Person · session · network

Catch the coached victim, too.

Scammers have the password and the OTP; what they cannot hide is the pattern around the payment. RuseTect now also reads the banking session (with consent), checks it is really the customer, links victims into campaigns, and answers with a pause, not a panic.

Session · coaching cues

Notice the call behind the payment

A live or video call, screen sharing, a remote access app, a pasted payee, long hesitation: a fourth evidence domain. Alone it only asks the customer; with payment or message evidence it holds the money for an analyst.

40 / 40session points on the digital arrest demo, held at 914 out of 1,000 · synthetic
Person · is it really them?

Stop SIM swap takeovers before the OTP

An unenrolled device right after a SIM swap gets no OTP and reverifies by video KYC. Typing and mouse rhythm check the person; account takeover chains (reset, new device, new payee, transfer) are caught in order.

0.89ROC-AUC of the typing check from one sentence, on 2,868 real typists it never saw (Aalto keystrokes)
Pause, not panic

Ask the one question that breaks the spell

"Has anyone said you are under digital arrest and must pay?" chosen from the evidence, in English or Hindi, with a 30 minute cool off. A duress PIN shows the scammer "payment successful" while the money is held and family is alerted.

30 mincool off before a paused payment can continue
Network · campaigns

See one scam hitting many people

Alerts link into a campaign when they share a payee, a payee identifier from the messages, or a near identical script, with the reason shown for every link.

3 wayssame payee, same identifier, same script
Network · across banks

Catch a mule once, flag it everywhere

A payee blocked by two analysts is published to a signed, hash chained ledger as an HMAC token. Other banks can match it without learning anything else. No blockchain needed.

Ed25519signatures on every ledger entry, verdict and evidence pack
Analysts in charge

Signed in, four eyes, STR drafted

Analysts act with their own keys, a block needs two different people, replays are rejected, and a ready to review STR draft is built from the evidence pack without sending data to any outside AI service.

2 analystsrequired for every block · hash chained audit log
What the model sees

See the whole ring.

A single transfer looks normal. The pattern around it does not.

  1. 01

    History, not just the payment

    31 features per payment, each computed only from earlier activity: money in then straight out, sudden fan-out, money flowing back.

  2. 02

    Reasons an analyst can use

    Every block lists its top drivers in plain words, such as "minutes since money came in: 0" or "new counterparty", ready for the STR draft.

  3. 03

    Rings by typology

    Offline benchmark against IBM's labelled synthetic rings: fan-in 90%, cycles 88%, gather scatter 87%, fan-out 86% caught. Bipartite rings are the hardest at 52%.

Victim payment mule 1 · 0 minmule 2 · 2 minmule 3 · 4 minmule 4 · 7 min Cash out
accounts in one L4 caseheld, block recommended to two analystsillustration
Why this model

Boosted trees, because of the data.

Each payment becomes 31 numbers about the accounts' past behaviour: how fast money moves on, how many new payees, how much flows back. Many are missing for new accounts, and laundering is under 0.2% of payments. Gradient boosted trees fit that shape well: they learn interactions without hand built crosses, handle missing values natively, train in seconds and score in well under a millisecond, and XGBoost gives per feature reasons for each alert.

Features matter more than the algorithm

XGBoost scores 0.172 PR-AUC on raw payment fields and 0.715 with account history features. The top four tree models sit between 0.699 and 0.715.

L1 is small on purpose

30 shallow trees approve about 80% of payments in about 0.3 ms while keeping about 99% of laundering for L2. The goal is speed with very few misses, not a final decision.

L2 averages XGBoost and LightGBM

Two boosters grown differently give a steadier score. In the audit, the live average scored 0.716 PR-AUC on the test period against 0.722 for its XGBoost half alone, so the case for it is robustness, not accuracy.

ModelTask · featuresPR-AUCStrengthsLimitsStatus
XGBoostPayments · account history0.715Best single score; native missing values; TreeSHAP reasonsNeeds the engineered history featureslive L1 + L2
LightGBMPayments · account history0.699Fast, compact; second opinionSlightly lower alonelive L2
Random ForestPayments · account history0.706Robust with little tuningLarge model, slower per paymentoffline blend only
CatBoostPayments · account history0.700Strong defaults, categorical handlingSlower to train here; no gainnot used
MLPPayments · account history0.574FlexibleNeeds scaling; weaker on tabular datanot used
Decision TreePayments · account history0.533Fully readableUnstable, lower ranking qualitynot used
Logistic RegressionPayments · account history0.132Simple, calibratedCannot learn interactionsnot used
XGBoostPayments · raw fields only0.172Same model without account history: shows where the signal comes fromreference
LightGBMAccount applications (BAF)0.190Slightly ahead of XGBoost (0.187); fastDifferent task: not comparable with payment scoreslive L0

PR-AUC (average precision) summarises precision across all recall levels. With 0.18% positives, a model that flags nothing is 99.8% accurate, so accuracy is meaningless here. Committed results from ml/bench_tabular.py and ml/bench_baf.py on synthetic data; a fresh comparison of more models (including TCN) is in the report. Details: docs/model-selection.md.

Specifications

Reported results.

Committed results from this repository's benchmark scripts, on synthetic data later in time than the training data. They do not show performance on real banking traffic.

Payment data
5,078,345IBM AMLworld HI-Small payments, 515,088 accounts, 5,177 laundering, 370 labelled rings
Onboarding data
1,000,000Feedzai BAF account applications, 1.1% fraud
Test protocol
Time ordered 60/20/20 split; thresholds set on validation; BAF trained on months 0 to 4, tested on 6 to 7
Offline blend (not live)
0.725PR-AUC; block precision 0.921, F1 0.702, with Random Forest and LSTM/Transformer in the blend
Rings caught (offline)
82 to 89%of the 192 test period rings get at least one block (range across two retrains)
Onboarding recall
0.534at a 5% false positive rate, with age fair thresholds
Latency (live path)
0.33 msmedian per payment including feature computation, one CPU core, replay of 60,000 payments
Streaming parity
100%same block decisions when the live engine and the offline pipeline score the same 60,000 payments with the same L1 + L2 models
Benchmarks

21 models, one scoreboard.

Same training rows, same threshold procedure, same held out test period. PR-AUC on laundering payments (0.18% of test traffic). Reported results from ml/bench_*.py.

Account history features matter most: XGBoost goes from 0.172 on raw payment fields to 0.715 with them. The GNN is tested with strictly past only graph data; published GNN results on this dataset let the test graph include future edges. Full report →

How to

Try it in three steps.

01

Load account history

Open the live console and load earlier payments, so every account has context, as it would in a bank.

Open console
02

Stream recorded payments

60,000 recorded test period payments from the synthetic IBM dataset flow through the live path. Watch clears, blocks, reasons and ring cases, checked against ground truth.

03

Call the API

Score your own payment or account application.

POST /api/v2/payment
{ "timestamp": "2022-09-09 10:00",
  "from_bank": "070", "from_account": "A1",
  "to_bank": "011",   "to_account": "B2",
  "amount_paid": 9300, "payment_format": "ACH" }
API docs
RuseTect

Stop the money,
not the customer.

Built and benchmarked on 6 million synthetic records. Real deployment would need retraining and revalidation on a bank's own data.