The open-source fraud engine that returns a verdict.
Verdict scores every event against rules you can read and answers allow / review / deny in under 100ms — a modular monolith you can split into services the day you outgrow it.
One request in. One verdict out.
An event arrives, features are read, rules fire, a score resolves — and the caller gets a decision plus the exact signals behind it. Nothing hidden.
{ "type": "payment.authorize", "amount": 4900.00, "currency": "ETB", "user": "usr_3f9a", "device": "new", "channel": "telebirr" }
Everything a fraud team reaches for.
In one deployable — not five SaaS invoices and a data-sharing agreement.
Real-time decision API
One synchronous call returns a verdict, a score, and every signal that fired — inside a hard latency budget.
Rules you can read
A declarative DSL, versioned in git, hot-reloaded and diffable in review. No black box to trust on faith.
when velocity.attempts > 4 then score +28
Velocity feature store
Redis-backed counters and rolling aggregates computed at write time — attempts per minute, spend per hour.
Weighted risk scoring
Combine rule hits and features into a transparent 0–100 score. Swap in an ML model behind the same port later.
score = 61 // 28 + 19 + 14 band = review // 35–69
Case management
Every review verdict opens a case. Analysts triage a queue, take action, and leave an audit trail.
Feedback loop
Chargebacks and analyst decisions become labels. Backtest a new rule against them before it touches prod.
Entity graph
Link users, devices, cards and phones to surface rings — the shared fingerprints one event can't reveal.
Local-market signals
Detectors Western tools skip — SIM-swap and OTP abuse, agent fraud, cash-on-delivery abuse — plus gateway connectors.
Self-hosted & private
Your data never leaves your infra. One Docker deploy today; the same code splits into services under load.
Declarative from the first line.
Rules, policies and connectors are code — reviewed, versioned and rolled back like everything else you ship.
// rules/card.authorize.vd — versioned, hot-reloaded rule r_velocity { when velocity.attemptsLast2m > 4 then score += 28, tag "velocity" } rule r_takeover { when device.firstSeen == true && geo.ipSimMismatch == true then score += 33, tag "takeover" }
A modular monolith whose seams are already service boundaries.
Seven bounded contexts, each owning its data, talking over an event bus and typed ports — never a shared table. Ships as one process. Every arrow that crosses a context is exactly where you'd cut to extract a service.
Ports & adapters
Every context depends on interfaces, not implementations. Redis, MySQL, Kafka and the ML scorer are swappable adapters.
Append-only verdict log
Decisions are events, never mutated. Replay the log to backtest a rule change or reconstruct any verdict.
Extract without rewrite
Scoring melting under load? It already talks over the bus. Move it to its own service; nothing upstream changes.
From a rules engine to a fraud platform — in public.
Each phase ships a coherent, usable slice. The order is deliberate: a decision loop first, then depth, then the human layer, then intelligence and the surfaces that make it easy to integrate — and now the hardening that makes it production-ready.
The decision loop
A verdict, end to end.
- Event ingest & normalize
- Declarative rules engine
- Sync decision API
- Redis velocity counters
- Append-only verdict log
Decisioning depth
Smarter, safer verdicts.
- Weighted risk scoring
- Step-up / challenge
- Block / allow / watch lists
- Versioned policies
- Idempotency & outbox
Human in the loop
Grey areas get a person.
- Case management
- Analyst audit trail
- Label + chargeback capture
- Operator dashboard
- Password auth · Postgres
Intelligence
Learn from every label.
- Analytics read-model
- Adaptive scoring model
- Rule & policy backtesting
- Entity graph & rings
- Per-user anomaly detection
Built to integrate
Wire it in, in an afternoon.
- MCP server (LLM-native)
- Outbound webhooks
- OpenAPI & Swagger UI
- API activity log
- Slack / Telegram / webhook alerts
Production-ready
Hardened for real traffic.
- Fail-closed config & container hardening
- Atomic idempotency & transactional outbox
- Schema migrations & indexes
- Observability, metrics & durable webhooks
- Auth hardening, scoped keys & revocation
- Load & failure testing
- Data retention & pruning
Scale & operability
Run it big, run it calm.
- Durable, throttled notifications
- Batch & async decisions
- Config-change audit log
- Dashboard / docs test coverage
- Cloud-backed, hot-swappable model
- Storage stats & disk reclaim
- Typed tables for high-volume reads
- SDK development (client SDKs)
Smarter signals
Catch more, explain it all.
- IP geolocation & impossible travel
- Device fingerprinting & cloning signals
- Trained ML scorer (synthetic data)
- Explainable per-feature reasons
Ecosystem & scale
Split, connect, grow.
- Broker event bus (Kafka/BullMQ)
- Distributed tracing
- Multi-tenant & RBAC
- Managed feature store
- Streaming ingest
Elegant is a constraint, not a coat of paint.
A contributor should be able to hold one context in their head. These are the rules the codebase enforces.
No shared tables
Contexts own their data and talk through events and typed ports. The schema can never become the coupling.
Dependency injection
Adapters arrive through constructors; a default instance is exported. Every unit tests against a mock, not real Redis.
Transactional integrity
Money, stock and case state move inside $transaction. A verdict is written, or nothing is.
Fail on purpose
Each policy declares fail-open or fail-closed. A degraded scorer never silently blocks or waves through.
Additive by default
New signals, outcomes and fields — never a breaking change to a live integration. Old callers keep working.
Slow work is async
Enrichment, graph updates and notifications run on queues. The decision path stays inside its budget.
Ship a verdict this afternoon.
Clone it, write three rules, and point your payment path at /v1/decisions. Grow into the platform on your own timeline.