Coerced-payer detection for digital arrest scams
Team ThreatForge · RAKSHAM AI Cybersecurity Hackathon (IIT Delhi × Amazon) · Track 02: AI-Driven Scam Pattern Recognition
Sameer Ahmed (team lead, Osmania University) · Bhargab Das · Bhukya Narender (IIT Jodhpur)
Thehro (ठहरो) means "wait". It's a safety layer for a bank's app. It notices when a customer is paying under pressure from a caller, holds only that payment, and brings in someone outside the call.
Watch the 51-second demo (MP4): a normal payment passes; a digital-arrest payment is held, warned aloud in Hindi and English, checked with "Is this caller real?", reviewed by the bank analyst with a 1930 report pack, and released once the call ends.
In a digital arrest scam, a fake police, CBI or customs officer keeps the victim on a voice or video call, sometimes for hours, and pushes them to "verify" their savings by transferring money. The victim pays from their own phone with their own PIN, so the bank sees a normal, authorised payment.
Most fraud controls look at the receiving account or slow down everyone's payments. Thehro looks at the payer. Is this person acting under pressure right now?
flowchart LR
subgraph PHONE["📱 Customer's phone · bank app + Thehro SDK"]
A1["Call active? (yes/no only)"]
A2["Typing pauses · bursts · paste · app switches"]
A3["On-device pressure model<br/>(score 0–1)"]
A4["Account number → SHA-256 hash"]
A2 --> A3
end
subgraph BANK["🏦 Bank's servers · Thehro scoring service"]
B1["Bank events<br/>FD broken · new payee · limit raised<br/>large transfers · amount vs usual"]
B2["Personal baseline<br/>(this user's normal pace)"]
B3["Mule network check<br/>(same account in other held payments)"]
B7["Customer's Safety Mode<br/>(own limit during calls)"]
B7 --> B6
B4["Rule guard<br/>no single signal can alert"]
B5["Gradient-boosted model"]
B6{"Decision<br/>T0 · T1 · T2 · T3"}
B1 --> B4
B2 --> B4
B3 --> B4
B4 --> B6
B5 --> B6
end
subgraph PEOPLE["👥 Response"]
C1["Customer<br/>warning / hold screen · spoken alert · Hindi<br/>caller check · safety questions"]
C2["Guardian<br/>alert, no amounts"]
C3["Bank analyst<br/>review queue · release / keep"]
C4["Report pack<br/>1930 · cybercrime.gov.in"]
end
A1 --> B4
A3 --> B4
A4 --> B3
B6 --> C1
B6 --> C2
B6 --> C3
C3 --> C4
sequenceDiagram
participant U as Customer (on a scam call)
participant P as Bank app + Thehro
participant S as Scoring service
participant G as Guardian
participant A as Bank analyst
U->>P: Enters payee, account, ₹2,50,000
P->>P: Measures timings, on-device score, hashes account
P->>S: POST /score (signals only, no text or audio)
S->>S: Bank events + baseline + mule check + model
S-->>P: T2 HOLD + reasons
P-->>U: "Payment on hold" (spoken aloud, Hindi/English)
S-->>G: Guardian alert
U->>P: Safety check: "Someone on the phone is telling me what to do" → Yes
P->>S: POST /checkin → escalated to T3
S-->>A: Case appears in the analyst queue
A->>A: Reviews, opens report pack (1930)
U->>P: Ends the call, confirms
A->>S: Release (or keep holding)
| Feature | What it does | |
|---|---|---|
| 📞 | Call detection | Checks whether a normal or VoIP (WhatsApp) call is active during the payment. Yes/no only, never who is calling |
| ⌨️ | Behaviour signals | Pauses between fields, the account number typed in bursts (someone reading it out) or pasted, switching apps mid-payment |
| 🧠 | On-device pressure model | A small logistic model runs inside the app. Only its score leaves the phone |
| 👤 | Personal baseline | After 3 ordinary payments, hesitation is compared with this user's normal pace, not a global average |
| 🏦 | Bank-side events | FD broken today, new payee, transfer limit raised, repeat large transfers, amount vs usual, share of balance |
| 🕸️ | Mule account detection | The account number is hashed on the phone. If the same account already received other held payments, it's flagged across customers |
| 🛡️ | Rule guard + ML model | No single signal can raise an alert. A gradient-boosted model refines the tier within the guard |
| ⏸️ | Tiered response | Pass, warn, hold until the call ends, or analyst review. Only the risky payment is touched |
| 🔊 | Spoken alert | The hold message is read aloud, so a victim still on the call hears it |
| 🇮🇳 | Hindi warning screen | Title, reasons and safety advice switch to Hindi in one tap |
| 🔒 | Screen-share shield | While a real call is active, the app hides itself from screen sharing and screen recording (Android FLAG_SECURE), so a scammer watching over a video call sees a black screen, not the payment or the warnings |
| 🛡️ | Safety Mode | The customer (or their family) sets their own rule: hold any payment above ₹X made during a call. Consent-based protection for senior citizens |
| 🕵️ | "Is this caller real?" | The user taps who the caller claims to be (CBI, Police, Customs, TRAI, Bank, RBI) and the app shows and speaks the facts: "CBI never arrests anyone on a call… call 1930" |
| ❓ | Safety check | On a hold, three yes/no questions ("Did they say you are under arrest?"). Any "yes" sends the payment to a bank officer. Kept off the warning screen so genuine customers aren't questioned |
| 👨👩👧 | Guardian alert | A family member gets an alert (no amounts, can't move money) and a one-tap message |
| 🧑💼 | Analyst queue | Live dashboard with counts, every held case, reasons, release / keep |
| 📈 | Live dashboard and export | Charts of decisions and the most common warning signs; download every case as CSV; save any report pack as PDF |
| 📴 | Offline fallback | If the bank's scoring service can't be reached, the app applies a simple on-phone rule: a large payment during a call still gets a warning |
| 📄 | Report pack | A ready-to-file summary for the 1930 helpline / cybercrime.gov.in, plus a one-tap 1930 dialer in the app |
| 📊 | Built-in evaluation | /metrics scores 20,675 held-out payments and lists failure cases; /evaluate scores any labelled batch |
| Tier | When | What the customer sees |
|---|---|---|
| T0 Pass | Few or no signals | Nothing. Payment goes through |
| T1 Warn | 2+ signals, medium risk | Plain-language reasons and one question: "Do you still want to make this payment?" Yes sends it, No moves it to a hold |
| T2 Hold | 3+ signals, high risk, or the customer's own Safety Mode rule | Payment held, spoken alert, guardian alerted. Released only after confirming with no call active |
| T3 Review | Highest risk and ≥ ₹1,00,000, or the customer says a caller is directing them | Held until a bank analyst reviews it |
20,675 fresh payments (seed 2026) the model never saw (training seed 7), scored with the full decision logic.
| Metric | Result |
|---|---|
| Scam payments flagged (warn or hold) | 92.8% |
| Scam payments held | 64.0% |
| Genuine payments flagged | 1.1% |
| Genuine payments held | 0.17% |
| Holds that were real scams (precision) | 87.6% |
| Scams with the call hidden, still flagged | 95.0% |
| Adaptive scams (small amounts, no call), flagged | 70.7% |
| Main weakness: genuine large payments flagged | 70.7% |
Same 20,675 test payments, three approaches:
| Approach | Scams flagged | Scams held | Genuine held | Precision of holds |
|---|---|---|---|---|
| Rules only | 92.8% | 76.3% | 0.32% | 81.7% |
| Model only | 69.9% | 56.8% | 0.08% | 92.6% |
| Thehro (rules + model) | 92.8% | 64.0% | 0.17% | 87.6% |
The model halves wrongful holds (65 down to 34 genuine customers per 20,300) while keeping 92.8% of scams flagged. The model alone would miss 30% of scams, which is why the rule guard stays in front. The scams the model moves from "hold" to "warn" still see the warning and the safety check.
Confusion matrix (held): 240 scams held, 135 scams not held (most still warned), 34 genuine held, 20,266 genuine passed. Model alone: PR-AUC 0.88, ROC-AUC 0.997 (synthetic data, so expect lower on real data).
2 team members, 4 runs: the same ₹5,000 payment made calmly, then while a teammate played a "CBI officer" reading the account number aloud and rushing them.
| Average per run | Normal | Under pressure |
|---|---|---|
| Pause between fields | 4.1 s | 20.4 s |
| Account number typed in bursts | 1.5 | 3.0 |
| App switches | 0 | 2.0 |
| Session length | 68 s | 170 s |
| On-device pressure score | 0.05 | 0.80 |
The on-device model flagged 1 of 2 pressured runs and 0 of 2 normal runs.
Limits, stated plainly: the test data is synthetic, so real-world precision would be lower because real scams are rarer. The on-device model is trained on synthetic sessions. The volunteer pilot is tiny and role-played. Bank events are simulated with demo controls. A bank pilot in shadow mode is the next step.
- Never collected: call audio, call logs, phone numbers, contacts, messages, typed text.
- The account number leaves the phone only as a SHA-256 hash, used to spot mule accounts.
- The phone sends only a call yes/no, timings, counts and the on-device score.
- No single signal acts alone. Holds are reversible, with no automatic blocking and no accusation.
- Every alert explains itself in plain language, never with a bare score.
- The guardian sees an alert only: no amounts, and they can't move money.
- A human analyst reviews the highest-risk cases.
thehro/
├── app/ Android app (Kotlin, Jetpack Compose, min SDK 26)
│ └── app/src/main/java/com/threatforge/thehro/MainActivity.kt
│ signals, on-device model, transfer + result screens
├── server/
│ ├── main.py FastAPI scoring service, model, pages, evaluation
│ ├── test_main.py automated tests (pytest)
│ └── requirements.txt
├── .github/workflows/tests.yml runs the tests on every push
├── ondevice/
│ └── train.py trains the on-device pressure model, prints its weights
└── README.md
| Endpoint | Purpose |
|---|---|
POST /score |
Score a payment and return the tier, reasons (English and Hindi), guardian and case ID |
POST /checkin |
The customer's safety-check answers. Any "yes" escalates to analyst review |
POST /confirm |
Re-confirm a held payment (refused while a call is active) |
GET /analyst |
Analyst dashboard (counts, charts) and queue, release / keep |
GET /cases.csv |
Export every case as CSV |
GET /guardian/{case_id} |
Guardian alert page |
GET /report/{case_id} |
Report pack for 1930 / cybercrime.gov.in |
GET /baseline/{user_id} |
The user's learned normal pace |
GET /metrics · /metrics.json |
Held-out evaluation: rules vs model vs combined, confusion matrices, failure cases |
POST /evaluate |
Score a labelled batch (e.g. a jury test kit) |
GET /study/summary |
Volunteer study comparison |
GET /docs |
Interactive API docs |
1. Server (Python 3.11+)
cd server
pip3 install -r requirements.txt
uvicorn main:app --port 80002. App (Android Studio): open app/, run on an emulator or phone, then forward the port:
adb reverse tcp:8000 tcp:8000The app's server URL defaults to http://localhost:8000.
3. Demo journey
- Normal payment (₹6,000, all switches off): passes.
- Scam payment (₹2,50,000, FD broken + new payee + limit raised + simulate call, 1 large transfer): held, reasons shown and spoken.
- Tap हिंदी to see the Hindi screen. Answer the safety check "Yes" and it goes to analyst review.
- Open
/analystto see the case, then Report pack. - Turn off the call and confirm, or release from the analyst page: the payment goes through.
- Send ₹6,000 to the same account: flagged as a possible mule account.
- Turn on 🛡 Safety Mode (₹20,000), simulate a call and send ₹25,000. It's held by the customer's own rule. Tap CBI under "Is this caller real?".
Demo mode skips the 4-hour cooling period so the release can be shown live. Bank events are simulated with the demo switches.
cd server && pytest -q19 automated tests cover the core guarantees: a normal payment passes, the scam pattern is held with reasons, no single signal can alert, release is refused during a call, the safety check escalates, a "No" on a warning moves it to a hold, analyst release, mule detection (and that a mule match alone doesn't alert), Safety Mode limits, the on-device score as one vote only, invalid payloads rejected, and the model reducing false holds compared with rules alone, and CSV export. They run on every push via GitHub Actions.
What the prototype does today, and what a bank deployment adds:
| Area | Prototype today | Bank deployment |
|---|---|---|
| Enforcing the hold | Scoring service returns the decision; the demo app shows it | Thehro is advisory: the bank's payment-authorisation system enforces the hold, with a server-side state machine PENDING → HELD → RELEASED / CANCELLED |
| Storage | Cases and baselines persist to files and survive restarts | PostgreSQL with transactions and unique constraints; retries are idempotent so a payment can't be released twice |
| Analyst access | Open demo page | Authenticated, role-based access; every release, keep and override in a tamper-resistant audit log |
| Failure handling | If scoring is unreachable, the app falls back to an on-phone rule (large payment during a call gets a warning; others continue) | Defined timeout and fallback: if scoring is down, payments above a limit made during a call fall back to a warning, never a silent pass |
| Signals | Measured within the payment screen only | Explicit consent, accessibility-aware baselines (slow typists, screen readers), missing signals never treated as fraud |
| Models | Trained on synthetic data | Retrained on consented sessions and confirmed fraud labels; calibrated thresholds tuned to the bank's cost of a false hold vs a missed scam; scenario-level train/test splits |
| Operations | Manual runs | Latency monitoring (p50/p95/p99), error and timeout rates, rate limiting, secret and dependency scanning |
- Who runs it: the bank's fraud-risk team. The digital-banking team owns the app screens.
- How it plugs in: an SDK inside the bank's existing app, plus a scoring service on the bank's own servers, called before payment authorisation.
- Rollout: shadow mode (score silently), then warnings only, then holds for opted-in senior citizens, then scale with analyst capacity.
- Next: retrain the server and on-device models on consented real sessions and confirmed fraud labels.
- AI assistance: an AI coding assistant (Claude, Anthropic) was used during the hackathon, as declared on the disclosure form. No other AI tool was used.
- Pre-existing code: the Round 1 evaluation script (synthetic data generator and rule scoring) was written before the finale and declared on the pre-event disclosure form. All product code in this repository was written on-site.
- Data: fully synthetic, generated by our own code. No bank, customer or platform data.
- Libraries: FastAPI, Uvicorn, scikit-learn, NumPy, pandas, Jetpack Compose.