ShieldPay
Built by Team Sentinel for Squad Hackathon 3.0 (May 2026), where it placed in the top 50 of 600 teams. The interesting decision wasn't the model; it was refusing to stop at a number.
- Status
- Hackathon prototype — runs locally on demo data; not used by real merchants
- Team
- Four people: API, ML, frontend and integration/agent
- My part
- squad/, agent/, rag/, network/, ml/retrain.py, synthetic data
- Tests
- 19 integration tests (pytest + httpx)
- Stack

Governing idea
A risk score is not a decision. It needs reasons, a fallback and a way to be corrected.
The problem
Squad merchants receive a webhook for every settled payment, but nothing that helps them judge it: no scoring, no investigation, no way to act on a suspicious pattern or learn from a confirmed one.
How the idea changed
The first proposal was a single Isolation Forest over webhook payloads. It produced a number — and a merchant can't do much with 0.73. The design shifted four times during the hackathon.
- Investigation over scoring: an agent calls analysis tools and returns a verdict with written reasoning.
- Local context: a small retrieval corpus of twelve documents on Nigerian fraud patterns, queried before each agent call.
- Reliability: a deterministic orchestrator that uses the same tools, so an LLM outage never disables the product.
- Shared memory: device fingerprints confirmed as fraud at one merchant are checked for every other merchant.
The governing decision
ShieldPay sits behind the webhook, outside the payment path. Scoring happens after settlement, asynchronously, so it adds no latency to checkout — and it can be dropped in behind any merchant without touching their flow.
The system
Every webhook is verified with HMAC-SHA512, enriched through Squad's verify endpoint and checked against the fingerprint store. Three or more hits block immediately. Everything else goes to a dispatcher that gives the Groq-hosted agent five seconds; on timeout or error, the deterministic path runs instead. Both call the same four tools — velocity, anomaly score, amount deviation, merchant profile — and every verdict is tagged with the path that produced it.
Choose a scenario to follow a transaction through.
Diagram 01
From a webhook to a verdict
A ₦950,000 payment at 02:30 from a new device.
HMAC-SHA512 signature check. Invalid signatures are rejected with 401.
Every stage, in words
- 01Verify (squad/hmac.py) — HMAC-SHA512 signature check. Invalid signatures are rejected with 401.
- 02Enrich (Squad /transaction/verify) — Pull the full transaction from Squad, flatten it, convert kobo to naira, hash user identifiers.
- 03Fingerprints (network/fingerprints.py) — Check device and IP against fingerprints confirmed as fraud at any merchant.
- 04Dispatch (agent/dispatcher.py) — Give the LLM agent five seconds. On timeout, rate limit or connection error, run the deterministic orchestrator.
- 05Tools (agent/tools.py) — Velocity, anomaly score (IsolationForest + XGBoost over 7 features), amount deviation, merchant profile.
- 06Verdict (SAFE · SUSPICIOUS · BLOCK) — A verdict, a risk score, the signals used and written reasoning, tagged with its source.
- 07Act (dashboard · refund) — Verdicts land in the merchant dashboard. Risk above 0.92 triggers an automatic refund through Squad.
- 08Learn (ml/retrain.py) — Merchant labels queue a retrain; a candidate model deploys only if it passes a shadow test.

Limits of the model
The models were trained on roughly ten thousand synthetic transactions with injected fraud patterns — there is no real fraud data behind them. The ensemble weights (40% IsolationForest, 60% XGBoost) and the verdict thresholds were set by hand. The agent's reasoning is an explanation of the signals it used, not evidence that a transaction is fraudulent. The dashboards in the screenshots run on seeded demo data.
Those limits are why retraining is gated: a candidate model is only deployed if it passes a shadow test against the current one, so a noisy batch of labels can't quietly make scoring worse.
What broke, and what changed
Integration exposed real faults. Training and inference had been built on two different feature sets — the saved models expected raw webhook fields while the retraining script produced engineered features. The fix moved everything to engineered features and added an online function that computes them for a single live transaction from that user's history.
The retraining loop also had mismatched model filenames and table names that would have crashed it before it produced anything; those were fixed and the loop tested end to end.
Current state
A complete hackathon build that runs with Docker Compose, with or without a Groq key. Taking it further would need multi-tenant auth, rate limiting and audit trails, streaming feature computation, and a retrieval corpus that grows from confirmed cases rather than twelve seed documents.