Aller au contenu

C. Velocity and behaviour change

Ce contenu n’est pas encore disponible dans votre langue.

Fixed thresholds treat every customer the same. Velocity and behaviour rules do something smarter: they compare a customer with their own history. A GHS 20,000 week is routine for a wholesaler and alarming for a student.

These rules catch:

  • Accounts that have been taken over — a fraudster spends very differently from the owner.
  • Accounts that have been sold or rented to criminals — a quiet account suddenly becomes busy.
  • Pass-through accounts — money arrives and leaves almost immediately, which is how mule accounts are used.

SpeculaPro builds a profile for each customer and account — typical counts, typical amounts, typical hours — and keeps it up to date with every transaction. See Feature store for how this works.

ID Rule Mode Status
C1 Transaction count spike vs profile RT/PE Ships (core, post-event)
C2 Value spike vs profile RT/PE Ships (core, post-event)
C3 Dormant account reactivated with high activity PE/SCH Ships (core, post-event)
C4 New account, high value early RT/PE Ships (core, post-event)
C5 Rapid in-and-out (pass-through) PE Ships (core, post-event)
C6 Unusual hour for this customer RT Ships (fraud, real-time)
C7 Burst of payouts RT Ships (fraud, real-time)

What it looks for: an account making many more transactions in a day than it usually does: a takeover, a newly recruited mule, or a business that has outgrown its profile.

Example

With the defaults (multiplier 5, min_count 10, min_baseline_days 30), for an account that made 60 transactions in the 29 days before today (about 2 a day):

  • ✅ Fires: 12 transactions in the last 24 hours: at least 10, and more than 5 × 2.07.
  • ❌ Does not fire: 9 transactions in a day (under min_count), however quiet the account usually is.
  • ❌ Does not fire: an account opened 10 days ago (younger than min_baseline_days), or one with no opening date.

Parameters

Parameter Meaning How to tune
multiplier How many times the account’s average day counts as a spike Lower values catch more; 3–10 are typical values to test
min_count Transactions in the last 24 hours, at least Stops a jump from 1 to 6 transactions alerting
min_baseline_days How old the account must be before the rule applies, in days (7 to 3,650) 30 days or more gives a stable average

The windows are fixed: the last 24 hours, against the 29 days before them. Every transaction counts, in either direction.

Mode and pack: post-event, in core 1.1.0.

False positives: festive seasons, salary days and month-end, when everyone is busier. Watch alert volume around these dates during shadow testing.

Data it needs: the account’s opening date (opened_at on the account), and a continuous transaction history.


What it looks for: a customer moving a much larger total than usual in a period.

Example

With multiplier 5 and window 7 days, for a customer whose weekly total is usually about GHS 2,000:

  • ✅ Fires: GHS 14,000 moved this week (7× normal).
  • ❌ Does not fire: GHS 6,000 in a week when rent was paid (3×).

Parameters

Parameter Meaning How to tune
multiplier How many times the usual value counts as a spike As C1
window The period totalled Days to weeks

Mode and pack: real-time or post-event.

False positives: one-off life events — buying a car, paying a deposit on land, funeral contributions. These are exactly what an analyst should confirm and close quickly; record the explanation in the case.

Data it needs: amount (base currency) and a continuous history.


C3 Dormant account reactivated with high activity

Section titled “C3 Dormant account reactivated with high activity”

What it looks for: an account that has been inactive for a long time suddenly receiving or sending significant amounts. Dormant accounts are attractive to criminals: they are already verified, and their owner is not watching them.

Example

With the shipped rule, dormant_days 180 and min_amount GHS 10,000:

  • ✅ Fires: an account opened two years ago, with nothing in the last 90 days, receives GHS 25,000 and starts sending it on.
  • ❌ Does not fire: the same account receives GHS 300.
  • ❌ Does not fire: an account opened four months ago and unused since; it is younger than 180 days.

Parameters

Parameter Meaning How to tune
dormant_days How old the account must be (the quiet period itself is fixed at 90 days) At least the 90-day quiet period, so only established accounts count
min_amount The transaction needed after reactivation to fire Set so small “test” transactions don’t trigger alerts

Mode and pack: post-event, or as a scheduled sweep.

False positives: customers returning from abroad, seasonal workers, students. A check of the customer’s contact details and recent KYC usually resolves these.

Data it needs: full history (so the dormant period can be seen), account status.


What it looks for: a newly opened account moving large amounts straight away. Criminals open accounts specifically to receive fraud proceeds and use them before they are noticed.

Example

With account_age_days 30 and threshold_amount GHS 20,000:

  • ✅ Fires: an account opened 9 days ago receives GHS 35,000.
  • ❌ Does not fire: an account opened 9 days ago receives a GHS 2,500 salary.

Parameters

Parameter Meaning How to tune
account_age_days How long an account counts as “new” 30–90 days are common
threshold_amount Amount that is unusual for a new account Lower for retail, higher for business onboarding

Mode and pack: real-time or post-event.

False positives: new business accounts receiving their opening balance, customers moving their salary to a new bank. Segment business customers separately.

Data it needs: account opening date (opened_at), amount.


What it looks for: money that arrives and leaves again almost immediately, with nearly the whole amount passed on. The account is a pipe, not a destination — the defining behaviour of a money mule.

Example

With out_pct_of_in 90%, window 24 hours and min_amount GHS 5,000:

  • ✅ Fires: GHS 18,000 arrives at 10:05; GHS 17,500 is sent to three other wallets by 13:30.
  • ❌ Does not fire: GHS 18,000 arrives and GHS 4,000 is spent over the next week.

Parameters

Parameter Meaning How to tune
out_pct_of_in Share of incoming money that leaves 80–95% are typical values to test
window How quickly it must leave Hours to a day or two
min_amount Minimum amount passed through Ignores small everyday transfers

Mode and pack: post-event.

False positives: customers who move their own salary from one account to another on payday, and payment aggregators. Check whether the outgoing destination is the customer’s own account.

Data it needs: amount, direction, counterparties.


What it looks for: a payout at an hour this customer does not use, for example 03:00 for someone who only ever pays during the day. Payouts while the owner sleeps are a common sign that someone else is using the account.

Example

With the defaults (min_history 30, max_prior_at_hour 0, min_amount 1,000), for a customer with 80 transactions in 90 days, all between 07:00 and 21:00:

  • ✅ Fires: a GHS 4,000 transfer at 02:40. None of the 80 was in the 02:00 hour, so the customer is asked to prove it’s them.
  • ❌ Does not fire: a GHS 50 airtime purchase at 02:40 (below min_amount).
  • ❌ Does not fire: a customer with only 12 transactions in 90 days (not enough history).
  • ❌ Does not fire, for now: any customer in the first weeks after the upgrade that introduced the hour counters, until those counters have seen 30 of their transactions.

Parameters

Parameter Meaning How to tune
min_history Transactions in the last 90 days before it applies, at least Higher means a better-known pattern and fewer alerts
max_prior_at_hour Earlier transactions at this hour of the day, at most 0 means “never at this hour”; 1 tolerates a single earlier one
min_amount Payout at or above (base currency) Keeps small late-night purchases out

An hour means a clock hour (02:00 to 02:59), and every transaction counts towards the history, in either direction.

Mode and pack: real-time, added in fraud 1.1.0 and made like-for-like in fraud 1.2.0. Like every real-time rule, it only sees payments sent to the decision API.

False positives: shift workers, and travellers in other time zones. A step-up is a light answer: the genuine customer confirms and pays.

Data it needs: accurate transaction timestamps (occurred_at) and enough history.


What it looks for: many outgoing payments in a few minutes. A genuine customer rarely sends five payments in ten minutes; someone emptying a wallet they’ve taken over, or a script, often does. It’s a velocity rule built for real time: it has to act while the burst is still happening.

Example

With max_payouts 5 and min_total GHS 1,000:

  • ✅ Fires: six transfers of GHS 400 to different wallets between 22:01 and 22:07. The fifth already fires (five payouts, GHS 2,000).
  • ❌ Does not fire: five airtime purchases of GHS 10 in ten minutes (total under min_total).
  • ❌ Does not fire: four payouts of GHS 3,000 (fewer than max_payouts).

Parameters

Parameter Meaning How to tune
max_payouts Payouts in 10 minutes, this one included, from which it fires Above what your busiest genuine customers do: merchants and agents may need a separate rule
min_total Smallest 10-minute total that counts Keeps bursts of tiny payments (airtime, transport) quiet

Mode and pack: real-time, fraud pack. Ships today. It answers step_up, so a genuine customer passes with an OTP while a fraudster without the phone is stopped.

False positives: businesses paying staff or suppliers in a batch, and customers splitting a bill. Consider a suppression for a known business, or a segment check in a custom version.

Data it needs: direction and amount, with payouts sent through a real-time decision as they happen. A backlog sent late isn’t counted; see Late and backdated data.