Skip to content

smurftyy/ShieldPay

Repository files navigation

ShieldPay

Real-time fraud detection for Squad merchants.

FastAPI Python XGBoost Groq RAG Tests

Built by Team Sentinel for Squad Hackathon 3.0 — Smart Systems: The Intelligent Economy track, organised by HabariPay/GTCO.


Quick start

Run with Docker (recommended)

cp .env.example .env
# Add your GROQ_API_KEY — free at console.groq.com
# Add SQUAD_SECRET_KEY and SQUAD_PUBLIC_KEY from sandbox.squadco.com
docker-compose up --build

GROQ_API_KEY is optional. Without it, ShieldPay uses its built-in deterministic rule engine — all Squad integrations and the dashboard remain fully functional.

Demo flow (6 steps):

  1. Start the stack: docker-compose up --build
  2. Seed multi-merchant demo data:
    BASE_URL=http://localhost:8011 python backend/seed_multi_merchant.py
  3. Open the dashboard: http://localhost:3001
  4. Fire a fraud transaction:
    curl -X POST http://localhost:8011/webhook/simulate \
      -H "Content-Type: application/json" \
      -d '{"transaction_ref":"DEMO-001","amount":950000,"merchant_id":"demo_merchant","email":"test@demo.ng","timestamp":"2026-05-15T02:30:00"}'
  5. Watch the verdict appear in the transaction feed (dashboard auto-refreshes every 5 s).
  6. Click the verdict row → inspect the investigation log → submit a feedback label to queue a retrain.

Run manually (development)

# Backend
cd backend
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
python -m uvicorn main:app --port 8010 --reload

# Frontend (separate terminal)
cd frontend
npm install && npm run dev

What it does

ShieldPay sits behind Squad's payment webhooks. Every transaction that settles fires a webhook — ShieldPay intercepts it, runs it through an ML scoring pipeline and an LLM-backed investigation agent, and produces a verdict: SAFE, SUSPICIOUS, or BLOCK. High-confidence blocks trigger an automatic refund through Squad's API. Merchants confirm or dispute verdicts, and those labels feed back into the model to retrain it.

The system never sits in the transaction path. Scoring happens post-settlement, asynchronously — zero latency impact on checkout.


The problem

Nigeria loses an estimated ₦52 billion to payment fraud annually. Squad merchants have no native fraud intelligence layer — they receive transaction webhooks but have no tooling to score them, investigate patterns, or act on suspicious activity. ShieldPay is that layer.


Team Sentinel

Name Role Owns
Xanes (Oladapo) Squad API + Agent Core squad/, agent/, rag/, network/, ml/retrain.py, data/
Femi Backend + API Server api/, db/, main.py
obimz ML Engineer ml/model.py, ml/features.py
Daniel Frontend + Dashboard frontend/

System Architecture

flowchart TD
    A[Squad Payment Webhook] --> B[HMAC-SHA512 Verification\nsquad/hmac.py]
    B -->|Invalid signature| C[401 Rejected]
    B -->|Valid| D[Enrichment + Flatten\nsquad/investigation.py]
    D --> E[Squad /transaction/verify\nenrich_transaction]
    E --> F[Cross-Merchant Fingerprint Check\nnetwork/fingerprints.py]
    F -->|3+ fingerprint hits| G[Instant BLOCK\nrisk_score = 0.98]
    F -->|No prior match| H[FraudAgent Dispatcher\nagent/dispatcher.py]
    H --> I[RAG Query\nrag/retriever.py\nChromaDB — 12 fraud pattern docs]
    I --> J{Groq API healthy?}
    J -->|Yes| K[Groq LLM Agent\nLlama 3.3-70b\nagent/groq_agent.py]
    J -->|No / Timeout| L[Deterministic Orchestrator\nagent/deterministic.py]
    K --> M[Shared Tool Harness\nagent/tools.py]
    L --> M
    M --> N[check_velocity]
    M --> O[get_anomaly_score]
    M --> P[check_amount_deviation]
    M --> Q[get_merchant_profile]
    O --> R[ML Scoring Pipeline\nml/model.py + ml/features.py]
    N & O & P & Q --> S[Verdict\nBLOCK / SUSPICIOUS / SAFE]
    G --> T[Store Verdict + Tag Source\ndb/models.py]
    S --> T
    T -->|risk_score > 0.92| U[Auto-Refund\nSquad /transaction/refund]
    T --> V[Dashboard Feed\nGET /verdicts/]
    V --> W[Merchant Feedback\nPOST /feedback/txn_id]
    W --> X[Retrain Loop\nml/retrain.py]
    X -->|Shadow test passes| Y[Deploy New Models\nisolation_forest.pkl\nxgboost.pkl]
Loading

Sample Verdict

This is what ShieldPay produces for every transaction — not a score, an investigation:

{
  "transaction_ref": "TXN-9f3k2",
  "verdict": "BLOCK",
  "risk_score": 0.91,
  "source": "groq",
  "reasoning": "Three concurrent signals detected: velocity anomaly — 7 transactions in 6 minutes from the same device fingerprint; amount 380% above this merchant's 30-day average; odd-hours flag — 02:14 WAT. Cross-merchant fingerprint matched: device previously confirmed fraudulent at 2 other Squad merchants. Auto-refund initiated.",
  "signals": {
    "velocity_1h": 7,
    "amount_zscore_merchant": 3.8,
    "is_odd_hours": true,
    "fingerprint_hits": 2
  },
  "action": "auto_refund_initiated"
}

Agent Harness

flowchart LR
    A[dispatcher.py\nroutes the call] --> B{asyncio.wait_for\n5s timeout}
    B -->|Within timeout| C[groq_agent.py\nLlama 3.3-70b]
    B -->|Timeout / RateLimitError\nAPIConnectionError| D[deterministic.py\nrule-based orchestrator]
 
    C --> E[Tool-use loop\nmax 10 iterations]
    D --> F[Phase 1: velocity + anomaly\nalways runs]
 
    E --> G[tools.py\nshared harness]
    F --> G
 
    G --> H[check_velocity\nDB rolling count]
    G --> I[get_anomaly_score\nML pipeline]
    G --> J[check_amount_deviation\nz-score vs user history]
    G --> K[get_merchant_profile\ndeviation vs merchant avg]
 
    E -->|submit_verdict intercepted| L[Verdict\nsource: groq]
    F --> M[Score fusion\n40% anomaly + 25% velocity\n20% deviation + 15% merchant\nx1.25 odd-hours multiplier]
    M --> N[Verdict\nsource: deterministic]
Loading

ML Pipeline

flowchart TD
    A[Incoming Transaction\nfrom investigation context] --> B[engineer_features_online\nml/features.py]
    C[User Transaction History\nfrom DB — last 200 rows] --> B
 
    B --> D1[count_1h\nrolling velocity 1-hour]
    B --> D2[count_24h\nrolling velocity 24-hour]
    B --> D3[amount_zscore_user\nvs user history mean/std]
    B --> D4[amount_zscore_merchant\nvs merchant history mean/std]
    B --> D5[time_since_last_txn\nseconds since prior transaction]
    B --> D6[is_odd_hours\n00:00-05:00 WAT flag]
    B --> D7[merchant_txn_count\nprior visits to this merchant]
 
    D1 & D2 & D3 & D4 & D5 & D6 & D7 --> E[features_to_vector\nnumpy 1x7 array]
 
    E --> F[IsolationForest\nscore_samples + normalize]
    E --> G[XGBoost\npredict_proba]
 
    F --> H[IF Score x 0.40]
    G --> I[XGB Score x 0.60]
 
    H & I --> J[Ensemble Score 0.0 to 1.0]
    J -->|greater than 0.75| L[BLOCK]
    J -->|0.45 to 0.75| M[SUSPICIOUS]
    J -->|less than 0.45| N[SAFE]
Loading

Feedback and Retraining Loop

sequenceDiagram
    participant M as Merchant Dashboard
    participant F as feedback.py
    participant TL as TrustedLabel DB
    participant FP as FraudFingerprint DB
    participant R as retrain.py
    participant S as Shadow Test
 
    M->>F: POST /feedback/{txn_id} label: fraud / safe
    F->>TL: Store TrustedLabel weight based on source
    F->>FP: write_fingerprint() on fraud label
    F->>R: retrain_if_ready() as background task
 
    R->>R: should_retrain()? min 20 new labels OR 6 hours elapsed gated on 100 total
    R->>R: Fetch labeled transactions from DB engineer_features() batch
 
    R->>S: Train candidate model
    S->>S: Evaluate accuracy vs current baseline
 
    alt Shadow test passes
        R->>R: Deploy isolation_forest.pkl + xgboost.pkl
        R->>R: Log RetrainEvent deployed: true
    else Shadow test fails
        R->>R: Log RetrainEvent deployed: false keep current models
    end
Loading

Screenshots

Network Overview Transactions with verdicts Model Health with retrain history Knowledge Base Merchant Portal


How the idea developed

The initial proposal was simple: score Squad webhook payloads with an Isolation Forest model and flag anomalies. That version had a core problem — it produced a number. A risk score of 0.73 tells a merchant nothing. They still have to decide what to do with it, they still don't know why it fired, and there is no mechanism to make the system smarter over time.

First shift — investigation over scoring. Instead of a static pipeline that returns a number, the team built a FraudAgent: an orchestrator that receives a transaction, dynamically calls a suite of analysis tools, synthesises the results, and produces a verdict with a written reasoning string a merchant can actually read and act on.

Second shift — Nigerian context. A generic fraud model trained on synthetic data doesn't know that transactions between midnight and 5am in Nigeria carry elevated risk, what card-testing looks like in a Lagos market, or what CBN AML guidelines say about sub-threshold splitting. A RAG layer was added: 12 documents covering Nigerian-specific fraud patterns, loaded into ChromaDB, queried before every LLM agent call to give it grounded regional context.

Third shift — reliability. The LLM is a network call. It can fail, rate-limit, or time out during a live demo. Rather than accepting that risk, a deterministic fallback was built: a rule-based orchestrator that uses the exact same tool harness as the Groq agent and produces verdicts with identical shape and quality. Every verdict is tagged source: groq or source: deterministic. The system degrades invisibly.

Fourth shift — collective intelligence. A fraud fingerprint confirmed at one merchant shouldn't be invisible to every other merchant on Squad. A cross-merchant fingerprint store was added: device fingerprints and IP addresses tied to confirmed fraud are written to a shared table. Every new transaction is checked against it before the agent runs. Three or more matches triggers an instant block without touching the LLM.


Team contributions

Femi — Backend Architecture

Femi designed and built the entire server layer from the ground up. Every FastAPI route file across all nine endpoints, the complete Pydantic schema layer covering every request validation and response serialisation shape, SQLAlchemy ORM models for all five database tables with correct foreign key relationships, enum type definitions, and eager-load configurations. He built the database session management, dependency injection pattern, and the init_db() initialisation that runs on startup. The BackgroundTask pattern that makes the entire async pipeline possible — returning 200 immediately while investigation runs in the background — is his design decision, and it's what makes the webhook architecture work at all. He wired CORS, registered all routers, and built the simulate endpoint that makes live testing possible without a real Squad sandbox event. The entire API surface that the frontend and Squad integration depend on was designed, written, and owned by Femi.

obimz — Machine Learning

obimz owned every part of the ML system. He designed the full feature engineering pipeline in features.py — including the rolling 1-hour and 24-hour velocity counts using correct pandas windowing, per-user and per-merchant amount z-scores with proper minimum-sample guards, WAT odd-hours detection, time-since-last-transaction delta computation, and the strictly ordered features_to_vector() transformation that produces a consistent numpy array for model input. He built and trained both the IsolationForest and XGBoost models, tuned the 40/60 ensemble weighting that balances anomaly detection sensitivity with classification precision, and wrote the shadow testing framework inside the retrain loop — the mechanism that evaluates every candidate model against the current production baseline before deployment, protecting accuracy from label noise. His feature engineering is the foundation that every agent tool call ultimately depends on when scoring a transaction.

Daniel — Frontend

Daniel built the entire user-facing product. The admin dashboard with its live transaction feed polling every 5 seconds, animated KPI cards, model accuracy gauge that responds to retrain events, fraud pattern breakdown charts, and investigation log drawer that expands per-transaction reasoning. The merchant profile view with scoped transaction history, fraud rate tracking, total volume display, and top fraud signal chips. The splash screen entry flow with the ShieldPay brand identity, the simulate webhook demo panel built for live judging, and all the real-time polling logic managing seven separate data streams without race conditions. Every visual element the judges and merchants interact with is Daniel's work.

Xanes (Oladapo) — Squad Integration, Agent Core, Data Pipeline

Xanes built the Squad integration layer from scratch — transaction enrichment against Squad's verify endpoint, webhook payload flattening with kobo-to-NGN conversion, PII-safe user ID hashing, and the refund trigger. He assembled squad/investigation.py, the end-to-end pipeline that sequences enrichment, fingerprint lookup, agent dispatch, verdict storage, and conditional auto-refund into a single coherent flow. He implemented the FraudAgent dispatcher with its timeout and failover logic, the full Groq LLM agent loop with multi-turn tool-use handling and graceful fallback on max iterations, and the deterministic orchestrator with three-phase conditional tool execution, score fusion, and natural language reasoning generation. He built the RAG retriever with lazy ChromaDB initialisation and the full 12-document Nigerian fraud pattern knowledge base. He built the cross-merchant fingerprint store and the network hit detection logic. He wrote the ML retrain wiring that connects the feedback API to the retraining pipeline. He generated the ~10K synthetic transaction dataset with injected fraud patterns. He owns the pitch.


Build issues and resolutions

SyntaxError in ml/model.py — entire ML path dead on arrival

score_transaction() had a try: block that opened at 8-space indent then broke to 4-space indent mid-function with a bare import statement — not an except clause. Python raised SyntaxError before the module loaded. Every downstream caller was silently receiving the 0.5 fallback score on every transaction.

Resolution: Rebuilt score_transaction() from scratch with correct structure and removed the dead validation block that was shadowing the features parameter.


Two incompatible feature sets — training and inference disconnected

model.py used 8 raw webhook fields processed through label encoders. features.py had a separate set of 7 engineered signals. The pkl files on disk were trained on the raw set. The retrain script used the engineered set. They had never been connected, meaning the model in production and the model being retrained were operating on completely different inputs.

Resolution: Switched the entire pipeline to engineered features. The key addition was engineer_features_online() — a function that computes all 7 features for a single live transaction by querying the user's DB history at inference time, solving the online feature problem that had previously made engineered features impossible to use outside batch training.


Four bugs in ml/retrain.py — retrain loop would crash before producing any output

  • Saved models as iso_forest.pkl / xgb_model.pkl; inference loaded isolation_forest.pkl / xgboost.pkl
  • Raw SQL queried table squadwebhook_transactions; actual ORM table is transactions
  • SQL JOIN referenced trustedlabel.transaction_id; actual table and column are trusted_labels.txn_id
  • Velocity grouping used user_id; Transaction table stores user_id_hash All four fixed. The retrain loop is wired and tested end-to-end.

Integration test timing — Groq latency exceeded test waits

Test waits of 3.5–4.5 seconds were sized for local processing. When Groq rejected the test key and the system fell back to deterministic, the full round-trip took 5–7 seconds. Tests were asserting on verdicts that hadn't been written yet.

Resolution: Increased all background-verdict waits to 8 seconds.


HMAC test failure — test process couldn't read .env

The HMAC test posted with a correct signature but the test process was reading an empty SQUAD_SECRET_KEY while the server had loaded the real value from .env. Signatures never matched.

Resolution: Added load_dotenv(find_dotenv(usecwd=True)) at test module import time.


generate_transaction() NameError — synthetic data script broken

Function defined with only is_fraud as a parameter but referenced user_id and timestamp as bare local variables. Call sites passed positional arguments the signature didn't accept, including is_velocity=True which didn't exist on the function at all.

Resolution: Updated signature to generate_transaction(is_fraud, user_id, timestamp=None, is_velocity=False) and added card-testing amount reduction logic for velocity fraud patterns.


Local setup

Backend

cd backend
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env   # fill in GROQ_API_KEY and SQUAD_SECRET_KEY
uvicorn main:app --reload --port 8010

# Seed demo data (optional, run while server is up)
python seed_multi_merchant.py   # BASE_URL defaults to http://localhost:8010

First-time ML setup

cd backend
python data/generate.py
python -c "from ml.retrain import retrain_models_now; retrain_models_now()"

Frontend

cd frontend
npm install
npm run dev   # http://localhost:3000

Environment variables

GROQ_API_KEY=
SQUAD_SECRET_KEY=
SQUAD_BASE_URL=https://sandbox-api-d.squadco.com
DATABASE_URL=sqlite:///./shieldpay.db
CHROMA_PERSIST_DIR=./chroma_store
MODEL_DIR=./ml/models
ENABLE_RAG=true
AUTO_REFUND_ENABLED=true
RETRAIN_MIN_LABELS=20
RETRAIN_TIME_HOURS=6
RETRAIN_MIN_TOTAL=100

Running tests

cd backend
pytest -v --tb=short tests/test_integration.py

19 tests covering: webhook pipeline, HMAC verification, transactions and verdicts API, investigation logs, feedback and retrain triggering, manual refunds, stats aggregates, and deterministic fallback forcing.


Live demo

curl -X POST http://localhost:8011/webhook/simulate \
  -H "Content-Type: application/json" \
  -d '{
    "transaction_ref": "DEMO-001",
    "amount": 950000,
    "merchant_id": "demo_merchant",
    "merchant_name": "Demo Merchant",
    "channel": "card",
    "email": "demo@test.ng",
    "timestamp": "2026-05-15T02:30:00"
  }'

curl http://localhost:8011/verdicts/ | python -m json.tool

Branch strategy

main      ← final working build only
└── dev   ← integration branch
    ├── feat/daniel-ui
    ├── feat/femi-api
    ├── feat/obimz-ml
    └── feat/xanes-core

PRs into dev only. Merge to main once, before judging.


Stack

Layer Technology
Backend framework FastAPI
Database SQLite (SQLAlchemy ORM)
ML models scikit-learn (IsolationForest), XGBoost
LLM agent Groq — Llama 3.3-70b-versatile
Vector store ChromaDB + SentenceTransformer
Squad integration Squad Sandbox API (webhooks, verify, refund)
Frontend React, Tailwind CSS, Recharts
Testing pytest + httpx

About

Fraud intelligence dashboard for Squad SME merchants — ML scoring ensemble (IsolationForest + XGBoost), cross-merchant behavioral fingerprinting.

Resources

Contributing

Stars

4 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors