Docs/Rule Engine

Rule Engine

Define fraud detection rules in YAML. No code deploys. Rules hot-reload on save.

Basic Rule

Rules live in the rules/ directory. Each YAML file defines one rule with conditions, severity, and an action.

rules/velocity-phone.yml
# rules/velocity-phone.yml
name: "High velocity same phone"
description: "Flag transactions when same phone number sends too frequently"
severity: high
action: review  # allow | review | block

conditions:
  - type: velocity
    field: sender.phone
    window: 1h
    threshold: 5
    operator: ">"

  - type: amount
    field: amount
    threshold: 500000
    currency: XOF
    operator: ">="

# Both conditions must be true (AND logic)
# For OR logic, create separate rules
match: all

Condition Types

velocity

Count or sum transactions within a time window. Fields: sender.phone, sender.device_id, receiver.agent_id, sender.ip. Windows: 5m, 15m, 1h, 6h, 24h, 7d.

amount

Check transaction amount against a threshold. Supports currency-aware comparisons. Operators: >, >=, <, <=, ==, between.

time_of_day

Match transactions within a time range. Useful for flagging unusual hours. Timezone-aware.

geo

Check sender/receiver location. Supports radius matching ("within 500m of known fraud hotspot") and geo-fencing (country, city, polygon).

metadata

Match on arbitrary metadata fields. Supports exact match, regex, and list membership. Example: device_type == "emulator".

history

Check account history. "Account age < 24h", "first transaction ever", "sender has been blocked before". Queries PostgreSQL audit log.

Advanced: Agent Collusion Detection

Combine multiple condition types to catch complex fraud patterns. This rule detects when multiple distinct users cash out at the same agent during late hours.

rules/agent-collusion.yml
# rules/agent-collusion.yml
name: "Agent collusion pattern"
description: "Flag when multiple users cash out at same agent within short window"
severity: critical
action: block

conditions:
  - type: velocity
    field: receiver.agent_id
    window: 30m
    threshold: 3
    operator: ">"
    context: "distinct sender count"

  - type: time_of_day
    after: "22:00"
    before: "06:00"
    timezone: "Africa/Abidjan"

match: all

notifications:
  - channel: webhook
    url: "https://your-app.com/alerts"
  - channel: slack
    channel: "#fraud-alerts"

Testing Rules

Test rules against historical transactions before enabling them in production.

# Dry-run a rule against last 7 days of transactions
curl -X POST /v1/rules/test \
  -d '{
    "rule_file": "rules/agent-collusion.yml",
    "lookback": "7d",
    "limit": 1000
  }'

# Response: how many transactions would have been flagged
# {
#   "would_flag": 23,
#   "total_evaluated": 1000,
#   "false_positive_candidates": 4,
#   "sample_matches": [...]
# }