Aller au contenu

Pack contents

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

These are the rules that ship with SpeculaPro today: 24 in core 1.4.1, 11 in fraud 1.4.0, 14 in gh-psp 1.2.0 and 10 in gh-bank 1.1.0. Four of them (I1, I2, I3 and E4) are in both gh-psp and gh-bank, so there are 55 different rules. The values below are the pack defaults, exactly as installed in a cedi workspace (core and fraud install other amounts in a workspace whose base currency is one of nine others). Every rule arrives switched off.

Fifteen more packs follow a jurisdiction’s rules and have their own pages:

Pack Version Rules Page
global-fatf 1.2.0 8 Pack contents: FATF baseline
ng-psp, ng-bank 1.2.0 9 and 8 (15 different) Pack contents: Nigeria
uemoa-emi, uemoa-bank 1.1.0 and 1.0.0 8 and 8 (15 different) Pack contents: UEMOA
sl-psp, sl-bank 1.0.0 8 and 8 (14 different) Pack contents: Sierra Leone
lr-psp, lr-bank 1.0.0 9 and 8 (14 different) Pack contents: Liberia
gm-psp, gm-bank 1.0.0 8 and 8 (14 different) Pack contents: The Gambia
gn-emi, gn-bank 1.0.0 8 and 8 (14 different) Pack contents: Guinea
ke-psp, ke-bank 1.0.0 12 and 9 (18 different) Pack contents: Kenya

Some of their rules reuse a rule on this page with other numbers, by reference. All nineteen packs together hold 167 different rules. The values in the Sierra Leone, Liberia, Gambia, Guinea and Kenya packs are unverified research, marked to verify with counsel.

  • The 33 post-event rules run seconds after each transaction and raise alerts.
  • The 13 real-time rules (all of fraud, plus gh.sim_swap_payout and gh.kyc_tier_limit_realtime) run inside a real-time decision and answer block, review or step_up. Each hit also raises a fraud alert. They do nothing for transactions sent only through the ingest API.
  • The 9 scheduled rules run on a timetable over the stored transaction history and raise alerts. See Scheduled rules.

Amounts are in your base currency (cedis for Ghanaian workspaces). Time windows over 48 hours are rounded to whole days. See the features reference.

Key Library Name Pack Mode · action Alert about Severity · score Lists
core.large_transaction A1 Large single transaction core post-event · alert customer medium · 40 —
core.large_cash A2 Large cash transaction (CTR candidate) core post-event · alert customer low · 20 —
core.large_cross_border A3 Large cross-border transfer core post-event · alert customer medium · 35 —
core.structuring B1 Repeated deposits just below a threshold core post-event · alert customer high · 70 —
core.value_spike C2 Spending spike against the customer’s own history core post-event · alert customer medium · 45 —
core.dormant_reactivation C3 Dormant account suddenly active core post-event · alert customer medium · 45 —
core.new_account_high_value C4 New account receiving high value core post-event · alert customer high · 60 —
core.pass_through C5 Rapid pass-through of funds core post-event · alert customer high · 65 —
core.fan_in D1 Many senders into one account core post-event · alert customer high · 60 —
core.fan_out D2 One account paying many recipients core post-event · alert customer medium · 50 —
core.many_payers_one_counterparty D7 Many of our customers paying one counterparty core post-event · alert counterparty high · 65 —
core.high_risk_jurisdiction E1 High-risk jurisdiction core post-event · alert customer high · 60 high_risk_countries
core.sanctioned_party F1 Sanctioned party involved core post-event · alert customer critical · 100 —
core.pep_threshold F2 PEP transaction above threshold core post-event · alert customer high · 70 —
core.high_risk_customer F3 High-risk customer, large transaction core post-event · alert customer medium · 50 —
core.high_risk_mcc H4 High-risk merchant category core post-event · alert customer medium · 40 high_risk_mcc
core.small_deposits_large_withdrawal B3 Many small deposits, then one large withdrawal core post-event · alert account high · 65 —
core.round_amounts B4 Repeated round-figure payments in core post-event · alert account medium · 45 —
core.same_amount_many_recipients B5 Same amount sent to many recipients core post-event · alert customer medium · 50 —
core.count_spike C1 Transaction count spike against the account’s own history core post-event · alert account medium · 40 —
core.mule_fan_in_fan_out D3 Collect from many, pay out to many (mule account) core scheduled · alert account high · 75 —
core.circular_flow D4 Money going round in a circle core scheduled · alert account high · 70 —
core.counterparty_confirmed_case D6 Counterparty linked to a confirmed case core post-event · alert customer high · 70 —
core.declared_profile F5 Activity above the declared profile core scheduled · alert customer medium · 50 —
fraud.card_testing H1 Card testing: many small card payments fraud real-time · block customer high · 80 —
fraud.new_device_new_payee H2 New device paying a new payee a large amount fraud real-time · review customer high · 70 —
fraud.failed_auth_then_payout H3 Payout after repeated failed OTP or PIN fraud real-time · review customer high · 65 —
fraud.profile_change_then_payout H5 Payout soon after a profile or credential change fraud real-time · review customer high · 70 —
fraud.new_country E2 First transaction in or to a new country fraud real-time · step-up customer medium · 40 —
fraud.impossible_travel E3 Impossible travel fraud real-time · review customer high · 70 —
fraud.shared_device D5 Device shared by many customers fraud real-time · review customer high · 60 —
fraud.payout_burst C7 Burst of payouts fraud real-time · step-up customer medium · 50 —
fraud.sanctioned_party F1-RT Sanctioned customer or counterparty fraud real-time · block customer critical · 100 —
fraud.deny_listed_counterparty F4 Counterparty on your deny list fraud real-time · block customer high · 90 blocked_countries, blocked_mcc
fraud.unusual_hour C6 Payout at an hour this customer never uses fraud real-time · step-up customer medium · 40 —
gh.sim_swap_payout G3 Payout soon after a SIM swap gh-psp real-time · block customer critical · 85 —
gh.kyc_tier_limit_realtime A4-RT Decline above KYC tier limit gh-psp real-time · block customer medium · 50 —
gh.kyc_tier_single_limit A4 Transaction above KYC tier limit gh-psp post-event · alert customer medium · 45 —
gh.kyc_tier_daily_limit A5 Daily outflow above KYC tier limit gh-psp post-event · alert customer medium · 45 —
gh.split_cash_in_agents B2 Cash-in split across many agents gh-psp post-event · alert customer high · 70 —
gh.cash_out_after_cash_in G2 Cash-out right after cash-in gh-psp post-event · alert customer high · 60 —
gh.wallet_sweep_to_bank G4 Wallet swept to bank after many inbound payments gh-psp post-event · alert customer high · 60 —
gh.reversal_abuse G5 Repeated reversals or refunds gh-psp post-event · alert customer medium · 45 —
gh.night_large_momo G6 Large mobile money transfer at night gh-psp post-event · alert customer medium · 40 —
gh.agent_peer_anomaly G1 Agent cash-in volume far above peer agents gh-psp scheduled · alert agent medium · 55 —
gh.merchant_turnover I1 Merchant takings far above declared turnover gh-psp and gh-bank scheduled · alert customer medium · 55 —
gh.merchant_refund_ratio I2 Merchant with many refunds or chargebacks gh-psp and gh-bank scheduled · alert customer medium · 50 —
gh.personal_business_volume I3 Personal account trading like a business gh-psp and gh-bank scheduled · alert customer medium · 45 —
gh.border_region E4 Activity in a border or high-risk area gh-psp and gh-bank post-event · alert customer medium · 40 high_risk_regions
ghb.branch_structuring GB1 Cash deposits split across branches gh-bank scheduled · alert customer high · 75 —
ghb.fx_cash GB2 Large foreign-currency cash gh-bank post-event · alert customer medium · 40 —
ghb.cheque_then_cash GB3 Cheque credits withdrawn in cash gh-bank post-event · alert account high · 65 —
ghb.gip_new_payee_large GB4 Large instant transfer to a new payee gh-bank post-event · alert customer medium · 50 —
ghb.correspondent_high_risk GB5 Cross-border flow through a high-risk correspondent gh-bank post-event · alert customer high · 60 high_risk_correspondents
ghb.trade_sector_mismatch I4 Trade payments abroad outside the customer’s line of business gh-bank scheduled · alert customer medium · 55 —

Market-neutral rules for every institution. Thresholds are starting points: calibrate with backtesting before go-live. Amounts are starting points in Ghana cedis (GHS); workspaces in NGN, XOF, USD, EUR, SLE, LRD, GMD, GNF and KES get equivalent defaults at install (rounded; the large-transaction and large-cash rules take the local cash reporting threshold). In any other currency the cedi figures are installed as they are: set them for your currency before enabling.

Version 1.1.0 added eight rules: B3, B4, B5, C1 and D6 (post-event), and D3, D4 and F5 (scheduled). Version 1.2.0 makes B4’s round_to a choice of 100 or 1,000; the release that ships it also checks D4’s hop range when a version is saved.

Version 1.3.0 states that the pack’s amounts are in Ghana cedis (GHS) and adds equivalent starting amounts for workspaces whose base currency is NGN, XOF, USD or EUR; no rule’s logic changed. The values in each rule’s section below are the cedi defaults. In a currency listed in this table, installing or upgrading uses its amounts instead; in any other currency the cedi figures are installed unchanged and the pack’s row warns you. They are round starting points, not precise conversions: calibrate them before go-live. See Packs written in another currency.

Version 1.4.0 adds starting amounts for five more currencies: SLE, LRD, GMD, GNF and KES. They are the US dollar figures scaled at a rough exchange rate and rounded, so treat them as unverified starting points and review them for your customers before you enable a rule. No rule’s logic changed.

Version 1.4.1 changes one sentence: the explanation of A2 (large cash transaction) no longer names Ghana’s FIC, and tells the analyst to confirm the threshold with your financial intelligence unit’s current guidance. No expression, parameter or amount changed. Because the explanation is part of the rule, upgrading drafts a new version of A2 for approval, unless your workspace has changed the rule itself.

Default amounts by currency
Rule Parameter GHS (as written) NGN XOF USD EUR SLE LRD GMD GNF KES
core.large_transaction threshold 50,000 5,000,000 15,000,000 10,000 10,000 30,000 1,000,000 450,000 100,000,000 1,300,000
core.large_cash threshold 50,000 5,000,000 15,000,000 10,000 10,000 30,000 1,000,000 450,000 100,000,000 1,300,000
core.large_cross_border threshold 20,000 2,500,000 1,000,000 1,500 1,500 30,000 300,000 100,000 12,500,000 200,000
core.structuring threshold 10,000 1,250,000 500,000 800 800 20,000 150,000 60,000 7,500,000 100,000
core.value_spike min_amount 5,000 600,000 250,000 400 400 10,000 80,000 30,000 3,000,000 50,000
core.dormant_reactivation min_amount 2,000 250,000 100,000 150 150 3,000 30,000 10,000 1,250,000 20,000
core.new_account_high_value threshold 20,000 2,500,000 1,000,000 1,500 1,500 30,000 300,000 100,000 12,500,000 200,000
core.pass_through min_amount 10,000 1,250,000 500,000 800 800 20,000 150,000 60,000 7,500,000 100,000
core.pep_threshold threshold 10,000 1,250,000 500,000 800 800 20,000 150,000 60,000 7,500,000 100,000
core.high_risk_customer threshold 10,000 1,250,000 500,000 800 800 20,000 150,000 60,000 7,500,000 100,000
core.high_risk_mcc min_amount 1,000 125,000 50,000 80 80 2,000 15,000 6,000 750,000 10,000
core.small_deposits_large_withdrawal min_amount 5,000 600,000 250,000 400 400 10,000 80,000 30,000 3,000,000 50,000
core.round_amounts min_amount 1,000 125,000 50,000 80 80 2,000 15,000 6,000 750,000 10,000
core.same_amount_many_recipients min_amount 200 25,000 10,000 15 15 300 3,000 1,000 125,000 2,000
core.mule_fan_in_fan_out min_in_amount 5,000 600,000 250,000 400 400 10,000 80,000 30,000 3,000,000 50,000
core.circular_flow min_amount 5,000 600,000 250,000 400 400 10,000 80,000 30,000 3,000,000 50,000
core.declared_profile min_volume 5,000 600,000 250,000 400 400 10,000 80,000 30,000 3,000,000 50,000
core.declared_profile occupation_caps student 3,000; unemployed 2,000; pensioner 5,000; artisan 15,000; civil servant 20,000 student 400,000; unemployed 250,000; pensioner 600,000; artisan 2,000,000; civil servant 2,500,000 student 150,000; unemployed 100,000; pensioner 250,000; artisan 750,000; civil servant 1,000,000 student 250; unemployed 150; pensioner 400; artisan 1,250; civil servant 1,500 student 250; unemployed 150; pensioner 400; artisan 1,250; civil servant 1,500 student 6,000; unemployed 3,000; pensioner 10,000; artisan 30,000; civil servant 30,000 student 50,000; unemployed 30,000; pensioner 80,000; artisan 250,000; civil servant 300,000 student 15,000; unemployed 10,000; pensioner 30,000; artisan 80,000; civil servant 100,000 student 2,000,000; unemployed 1,250,000; pensioner 3,000,000; artisan 10,000,000; civil servant 12,500,000 student 30,000; unemployed 20,000; pensioner 50,000; artisan 150,000; civil servant 200,000

Large single transaction · library A1 · Unusually large value · alert about the customer · severity medium, score 40

Fires when a transaction is at or above threshold. If channels lists any channels, only those count; an empty list means every channel.

Parameter Meaning Default
threshold Threshold (base currency) 50,000
channels Only these channels (empty = all) []

Explanation shown to analysts: Transaction of {{txn.base_currency}} {{txn.amount}} is at or above the {{txn.base_currency}} {{params.threshold}} threshold.

Expression
txn.amount >= params.threshold && (size(params.channels) == 0 || txn.channel in params.channels)

Large cash transaction (CTR candidate) · library A2 · Cash reporting threshold · alert about the customer · severity low, score 20

Fires on a cash-type transaction (cash_in, cash_out, deposit or withdrawal) through a cash channel (cash, agent, branch or atm) at or above threshold.

Parameter Meaning Default
threshold Cash threshold (base currency) 50,000

Explanation shown to analysts: Cash transaction of {{txn.base_currency}} {{txn.amount}} meets the cash reporting threshold of {{txn.base_currency}} {{params.threshold}}. Confirm the threshold with your financial intelligence unit’s current guidance.

Expression
txn.type in ['cash_in', 'cash_out', 'deposit', 'withdrawal'] && txn.channel in ['cash', 'agent', 'branch', 'atm'] && txn.amount >= params.threshold

Large cross-border transfer · library A3 · Cross-border value · alert about the customer · severity medium, score 35

Fires when the counterparty’s country is known and isn’t home_country, and the amount is at or above threshold.

Parameter Meaning Default
threshold Threshold (base currency) 20,000
home_country Home country (ISO code) "GH"

Explanation shown to analysts: {{txn.base_currency}} {{txn.amount}} to/from {{txn.counterparty_country}} is above the cross-border threshold of {{txn.base_currency}} {{params.threshold}}.

Expression
txn.counterparty_country != '' && txn.counterparty_country != params.home_country && txn.amount >= params.threshold

Repeated deposits just below a threshold · library B1 · Structuring / smurfing · alert about the customer · severity high, score 70

Fires on a cash-in that is just below threshold (within margin, so GHS 9,000 to 9,999.99 by default) when the account has had at least min_count cash-ins in 48 hours adding up to the threshold or more.

Parameter Meaning Default
threshold Threshold being avoided 10,000
margin Margin below threshold (0.10 = 10%) 0.1
min_count Minimum cash-ins in 48h 3

Features: cash_in_count_48h (account, count, in, type cash_in, 48 hours); cash_in_sum_48h (account, sum, in, type cash_in, 48 hours).

Explanation shown to analysts: {{features.cash_in_count_48h}} cash-ins totalling {{txn.base_currency}} {{features.cash_in_sum_48h}} in 48h; this one ({{txn.base_currency}} {{txn.amount}}) is just below the {{txn.base_currency}} {{params.threshold}} threshold.

Expression
txn.type == 'cash_in' && txn.amount >= params.threshold * (1.0 - params.margin) && txn.amount < params.threshold && features.cash_in_count_48h >= double(params.min_count) && features.cash_in_sum_48h >= params.threshold

Spending spike against the customer’s own history · library C2 · Behaviour change · alert about the customer · severity medium, score 45

Fires on an outflow when the account’s outflows in the last 24 hours reach min_amount and are more than multiplier times its average daily outflow over the other 29 days of the last 30.

Parameter Meaning Default
multiplier Times the daily average 5
min_amount Ignore below (base currency) 5,000

Features: out_24h (account, sum, out, 24 hours); out_30d (account, sum, out, 30 days).

Explanation shown to analysts: Outflows of {{txn.base_currency}} {{features.out_24h}} in 24h are over {{params.multiplier}}× the 30-day daily average ({{txn.base_currency}} {{features.out_30d}} over 30 days).

Expression
txn.direction == 'out' && features.out_24h >= params.min_amount && features.out_30d > features.out_24h && features.out_24h > params.multiplier * ((features.out_30d - features.out_24h) / 29.0)

Dormant account suddenly active · library C3 · Dormant account reactivation · alert about the customer · severity medium, score 45

Fires when an account older than dormant_days has had no activity in the last 90 days except within the last 24 hours, on a transaction of at least min_amount.

Parameter Meaning Default
dormant_days Minimum account age (days); the quiet period is fixed at 90 days 90
min_amount Minimum amount (base currency) 2,000

Features: count_90d (account, count, 90 days); count_24h (account, count, 24 hours).

Explanation shown to analysts: No activity in the 90 days before today on an account {{account.age_days}} days old, then {{txn.base_currency}} {{txn.amount}}.

Expression
account.age_days > params.dormant_days && features.count_90d == features.count_24h && txn.amount >= params.min_amount

New account receiving high value · library C4 · Mule onboarding · alert about the customer · severity high, score 60

Fires on any transaction when the account’s age is known and at most max_age_days, and it has received at least threshold in 7 days.

Parameter Meaning Default
max_age_days Account age up to (days) 30
threshold Inflow over 7 days (base currency) 20,000

Features: in_7d (account, sum, in, 7 days).

Explanation shown to analysts: Account opened {{account.age_days}} days ago (within the {{params.max_age_days}}-day window) has received {{txn.base_currency}} {{features.in_7d}} in 7 days.

Expression
account.age_days >= 0 && account.age_days <= params.max_age_days && features.in_7d >= params.threshold

Rapid pass-through of funds · library C5 · Layering / pass-through · alert about the customer · severity high, score 65

Fires on an outflow when the account received at least min_amount in 24 hours and has sent out at least out_ratio of that in the same 24 hours.

Parameter Meaning Default
min_amount Minimum inflow (base currency) 10,000
out_ratio Out as share of in (0.9 = 90%) 0.9

Features: in_24h (account, sum, in, 24 hours); out_24h (account, sum, out, 24 hours).

Explanation shown to analysts: {{txn.base_currency}} {{features.in_24h}} came in and {{txn.base_currency}} {{features.out_24h}} went out within 24h.

Expression
txn.direction == 'out' && features.in_24h >= params.min_amount && features.out_24h >= params.out_ratio * features.in_24h

Many senders into one account · library D1 · Fan-in / collection account · alert about the customer · severity high, score 60

Fires on an inflow when the account has been paid by at least min_senders different senders in 7 days.

Parameter Meaning Default
min_senders Distinct senders in 7 days 25

Features: senders_7d (account, distinct counterparties, in, 7 days).

Explanation shown to analysts: {{features.senders_7d}} different senders paid this account in 7 days.

Expression
txn.direction == 'in' && features.senders_7d >= double(params.min_senders)

One account paying many recipients · library D2 · Fan-out / distribution · alert about the customer · severity medium, score 50

Fires on an outflow when the account has paid at least min_recipients different recipients in 7 days.

Parameter Meaning Default
min_recipients Distinct recipients in 7 days 25

Features: recipients_7d (account, distinct counterparties, out, 7 days).

Explanation shown to analysts: This account paid {{features.recipients_7d}} different recipients in 7 days.

Expression
txn.direction == 'out' && features.recipients_7d >= double(params.min_recipients)

Many of our customers paying one counterparty · library D7 · Mule / scam collection point · alert about the counterparty · severity high, score 65

Fires on a payment to an outside party when at least min_payers of your accounts have paid that same party in 7 days. The alert is about the counterparty.

Parameter Meaning Default
min_payers Distinct paying accounts in 7 days 15

Features: payers_7d (counterparty, distinct counterparties, out, 7 days).

Explanation shown to analysts: {{features.payers_7d}} of our accounts paid this counterparty in 7 days.

Expression
txn.direction == 'out' && txn.has_counterparty && !counterparty.is_own_account && features.payers_7d >= double(params.min_payers)

High-risk jurisdiction · library E1 · High-risk corridor · alert about the customer · severity high, score 60

Fires when the transaction’s country or the counterparty’s country is on the high_risk_countries list and the amount is at least min_amount.

Parameter Meaning Default
min_amount Minimum amount (base currency) 0

Needs the list high_risk_countries.

Explanation shown to analysts: Funds to/from {{txn.counterparty_country}}, which is on the workspace’s high-risk country list.

Expression
has(lists.high_risk_countries) && txn.amount >= params.min_amount && (txn.counterparty_country in lists.high_risk_countries || txn.country in lists.high_risk_countries)

Sanctioned party involved · library F1 · Sanctions · alert about the customer · severity critical, score 100

Fires on any transaction where the customer or the counterparty carries a current sanctioned designation.

No parameters.

Explanation shown to analysts: A party to this transaction carries a sanctioned designation.

Expression
customer.is_sanctioned || 'sanctioned' in counterparty.designations

PEP transaction above threshold · library F2 · Politically exposed person · alert about the customer · severity high, score 70

Fires when the customer is a PEP or a relative or close associate of one (pep or pep_rca) and the amount is at or above threshold.

Parameter Meaning Default
threshold Threshold (base currency) 10,000

Explanation shown to analysts: Customer is a PEP; transaction of {{txn.base_currency}} {{txn.amount}} is at or above {{txn.base_currency}} {{params.threshold}}.

Expression
customer.is_pep && txn.amount >= params.threshold

High-risk customer, large transaction · library F3 · Enhanced due diligence · alert about the customer · severity medium, score 50

Fires when the customer’s risk rating is in ratings, or they have a high_risk designation, and the amount is at or above threshold.

Parameter Meaning Default
threshold Threshold (base currency) 10,000
ratings Risk ratings ["high", "unacceptable"]

Explanation shown to analysts: High-risk customer (risk rating: {{customer.risk_rating}}); transaction of {{txn.base_currency}} {{txn.amount}}. A customer flagged only by a high_risk designation, with a lower rating, reads for example “High-risk customer (risk rating: medium); …”.

Expression
(customer.risk_rating in params.ratings || customer.is_high_risk) && txn.amount >= params.threshold

High-risk merchant category · library H4 · High-risk merchant · alert about the customer · severity medium, score 40

Fires on a card payment whose merchant category code is on the high_risk_mcc list and whose amount is at least min_amount.

Parameter Meaning Default
min_amount Minimum amount (base currency) 1,000

Needs the list high_risk_mcc.

Explanation shown to analysts: Card payment of {{txn.base_currency}} {{txn.amount}} at MCC {{txn.mcc}}, on the high-risk MCC list.

Expression
txn.mcc != '' && has(lists.high_risk_mcc) && txn.mcc in lists.high_risk_mcc && txn.amount >= params.min_amount

Many small deposits, then one large withdrawal · library B3 · Structuring / smurfing · alert about the account · severity high, score 65

Fires on an outgoing withdrawal, cash_out or transfer of at least min_amount when, over the 7 days before it, the account received at least min_deposits payments, the outgoing amount is at least withdrawal_ratio of what came in, and no single payment in was larger than max_deposit_share of the outgoing amount. The alert is about the account.

Parameter Meaning Default
min_deposits Payments in over 7 days, at least 10
withdrawal_ratio Withdrawal as share of the 7-day inflow (0.8 = 80%) 0.8
max_deposit_share Largest payment in, as share of the withdrawal, at most 0.2
min_amount Withdrawal at or above (base currency) 5,000

Features: in_count_7d (account, count, in, 7 days), in_7d (account, sum, in, 7 days), in_max_7d (account, max, in, 7 days).

Explanation shown to analysts: {{features.in_count_7d}} payments in totalling {{txn.base_currency}} {{features.in_7d}} over 7 days (largest {{txn.base_currency}} {{features.in_max_7d}}), then a single {{txn.type}} of {{txn.base_currency}} {{txn.amount}}.

Expression
txn.direction == 'out' && txn.type in ['withdrawal', 'cash_out', 'transfer'] && txn.amount >= params.min_amount && features.in_count_7d >= double(params.min_deposits) && txn.amount >= params.withdrawal_ratio * features.in_7d && features.in_max_7d <= params.max_deposit_share * txn.amount

Repeated round-figure payments in · library B4 · Structuring / round-figure placement · alert about the account · severity medium, score 45

Fires on an incoming payment of at least min_amount that is a whole multiple of round_to in your base currency, with no minor units, when the account has received at least min_count such round payments in 7 days, this one included. The alert is about the account.

Parameter Meaning Default
round_to Round to (100 or 1,000): one of the two 1,000
min_count Round payments in over 7 days, at least 5
min_amount This payment at or above (base currency) 1,000

Features: round100_7d (account, count, in, round to 100, 7 days), round1000_7d (account, count, in, round to 1,000, 7 days).

round_to is a choice of 100 or 1000 (since 1.2.0). Round-amount counters start with version 1.1.0, so payments from before that upgrade aren’t counted.

Explanation shown to analysts:

{{= string(int(params.round_to == 100 ? features.round100_7d : features.round1000_7d)) }} payments in of round multiples of {{txn.base_currency}} {{params.round_to}} in 7 days; this one is {{txn.base_currency}} {{txn.amount}}.
Expression
txn.direction == 'in' && txn.amount >= params.min_amount && txn.amount == double(int(txn.amount)) && int(txn.amount) % (params.round_to == 100 ? 100 : 1000) == 0 && (params.round_to == 100 ? features.round100_7d : features.round1000_7d) >= double(params.min_count)

Same amount sent to many recipients · library B5 · Distribution to mules · alert about the customer · severity medium, score 50

Fires on an outgoing payment by a customer whose type is in customer_types when, over the last 24 hours, they made at least min_recipients payments to at least min_recipients different recipients, averaging at least min_amount, with the largest no more than amount_tolerance above the average, and this payment no more than amount_tolerance below it.

Parameter Meaning Default
min_recipients Distinct recipients in 24 hours, at least 5
amount_tolerance Amount tolerance (0.05 = within 5%) 0.05
min_amount Average payment at or above (base currency) 200
customer_types Customer types ["individual"]

Features: out_count_24h (customer, count, out, 24 hours), recipients_24h (customer, distinct counterparties, out, 24 hours), out_avg_24h (customer, avg, out, 24 hours), out_max_24h (customer, max, out, 24 hours).

An approximation from running totals: it compares the largest payment and the current one with the 24-hour average, not every payment with every other.

Explanation shown to analysts: {{features.out_count_24h}} payments to {{features.recipients_24h}} different recipients in 24 hours, all close to {{txn.base_currency}} {{features.out_avg_24h}} (largest {{txn.base_currency}} {{features.out_max_24h}}).

Expression
txn.direction == 'out' && customer.type in params.customer_types && features.recipients_24h >= double(params.min_recipients) && features.out_count_24h >= double(params.min_recipients) && features.out_avg_24h >= params.min_amount && features.out_max_24h <= features.out_avg_24h * (1.0 + params.amount_tolerance) && txn.amount >= features.out_avg_24h * (1.0 - params.amount_tolerance)

Transaction count spike against the account’s own history · library C1 · Behaviour change · alert about the account · severity medium, score 40

Fires on any transaction when the account is at least min_baseline_days days old, has had at least min_count transactions in the last 24 hours, and that is more than multiplier times its average day over the 29 days before (the 30-day count minus the last 24 hours, divided by 29). Accounts with no opening date never match. The alert is about the account.

Parameter Meaning Default
multiplier Times the daily average 5
min_count Transactions in 24 hours, at least 10
min_baseline_days Account age before it applies (days) 30

Features: count_24h (account, count, 24 hours), count_30d (account, count, 30 days).

Explanation shown to analysts: {{features.count_24h}} transactions in 24 hours, over {{params.multiplier}}× this account’s daily average ({{features.count_30d}} in 30 days).

Expression
account.age_days >= params.min_baseline_days && features.count_24h >= double(params.min_count) && features.count_30d > features.count_24h && features.count_24h > params.multiplier * ((features.count_30d - features.count_24h) / 29.0)

Collect from many, pay out to many (mule account) · library D3 · Money mule / pass-through account · scheduled, daily 02:00 · alert about the account · severity high, score 75

Over the last window_hours, finds accounts paid by at least in_senders different senders, min_in_amount or more in total, which then (after the first payment in) paid at least out_recipients different recipients, passing on at least min_out_ratio of what came in. Only completed transactions with an account and a counterparty count. The alert is about the account.

Parameter Meaning Default
window_hours Window (hours) 72
in_senders Distinct senders, at least 10
out_recipients Distinct recipients afterwards, at least 10
min_in_amount Inflow at or above (base currency) 5,000
min_out_ratio Paid on, as share of the inflow (0.7 = 70%) 0.7

Each hit records senders, recipients, in_amount, out_amount, out_ratio, hours for the explanation, as {{features.<name>}}.

Explanation shown to analysts: {{features.senders}} senders paid {{txn.base_currency}} {{features.in_amount}} into this account, then {{features.recipients}} recipients were paid {{txn.base_currency}} {{features.out_amount}} ({{features.out_ratio}} of the inflow) within {{features.hours}} hours.

Runs the built-in scenario fan_in_fan_out (scenario:fan_in_fan_out) on the schedule daily 02:00, in your workspace’s time zone. See Scheduled rules.

Money going round in a circle · library D4 · Round-tripping / layering · scheduled, daily 03:00 · alert about the account · severity high, score 70

Over the last window_hours, finds money that left an account and came back to it in min_hops to max_hops hops, each leg at least min_amount, later than the one before, within window_hours of the first and within amount_tolerance of the first leg’s amount. A counterparty whose account number is one of your accounts is that account, so loops through several of your customers join up. Accounts that paid more than 25 different counterparties in the window aren’t walked through. The alert is about the account the loop starts and ends at.

Parameter Meaning Default
window_hours Whole loop within (hours) 168
min_hops Hops, at least (2 = straight back) 3
max_hops Hops, at most 5
amount_tolerance Each leg within (0.1 = 10%) of the first 0.1
min_amount Legs at or above (base currency) 5,000

Each hit records hops, amount_out, amount_back, hours, cycles for the explanation, as {{features.<name>}}, and path as {{subject.<name>}}.

min_hops can’t be more than max_hops: a version that sets them that way is refused when it is saved (since 1.2.0).

Explanation shown to analysts: {{txn.base_currency}} {{features.amount_out}} left this account and {{txn.base_currency}} {{features.amount_back}} came back {{features.hours}} hours later after {{features.hops}} hops (loops found in the window: {{features.cycles}}).

Runs the built-in scenario circular_flow (scenario:circular_flow) on the schedule daily 03:00, in your workspace’s time zone. See Scheduled rules.

Counterparty linked to a confirmed case · library D6 · Known bad actor · alert about the customer · severity high, score 70

Fires on a transaction of at least min_amount with a counterparty whose latest case, closed with one of dispositions within the last lookback_days days, was about the counterparty itself or about your own account or customer behind the same account number. Post-event only.

Parameter Meaning Default
lookback_days Case closed within (days) 365
dispositions Case outcomes that count ["true_positive_no_report", "report_filed", "customer_exited"]
min_amount Amount at or above (base currency) 0

Explanation shown to analysts:

{{txn.base_currency}} {{txn.amount}} {{txn.direction}} with a counterparty whose case was closed as {{counterparty.confirmed_case_disposition}} {{= string(int(counterparty.confirmed_case_days)) }} days ago.
Expression
txn.has_counterparty && counterparty.confirmed_case_days >= 0.0 && counterparty.confirmed_case_days <= double(params.lookback_days) && counterparty.confirmed_case_disposition in params.dispositions && txn.amount >= params.min_amount

Activity above the declared profile · library F5 · Profile inconsistency · scheduled, daily 04:00 · alert about the customer · severity medium, score 50

Over the last period_days, sums each customer’s completed transactions in direction (any means in and out), scales the total to 30 days, and compares it with their declared monthly volume (when declared in your base currency) or, with none, with the ceiling in occupation_caps for their occupation. Fires when the scaled total is more than multiplier times that figure and the period’s total is at least min_volume. customer_types and segments narrow it; empty means all. Customers with neither a declaration nor a listed occupation are skipped.

Parameter Meaning Default
period_days Period (days) 30
multiplier Times the declared monthly volume 3
min_volume Turnover at or above (base currency) 5,000
direction Count money in, out, or both "any"
customer_types Customer types (empty = all) ["individual"]
segments Segments (empty = all) []
occupation_caps Expected monthly turnover by occupation, when none was declared (base currency) {"student": 3000, "unemployed": 2000, "pensioner": 5000, "artisan": 15000, "civil servant": 20000}

Each hit records volume, monthly_volume, declared, ratio for the explanation, as {{features.<name>}}.

The occupation ceilings are illustrations. Agree your own with your MLRO before relying on this rule.

Explanation shown to analysts: Turnover of {{txn.base_currency}} {{features.volume}} over {{params.period_days}} days (about {{txn.base_currency}} {{features.monthly_volume}} a month) is {{features.ratio}}× the declared or expected {{txn.base_currency}} {{features.declared}} a month.

Runs the built-in scenario declared_volume (scenario:declared_volume) on the schedule daily 04:00, in your workspace’s time zone. See Scheduled rules.

Rules for the real-time decision API: card testing, account takeover, new device or payee, impossible travel, shared devices and payout bursts. They answer block, review or step_up before the money moves. Thresholds are starting points: run them in shadow first and tune against your own traffic. Amounts are starting points in Ghana cedis (GHS); workspaces in NGN, XOF, USD, EUR, SLE, LRD, GMD, GNF and KES get equivalent defaults at install (rounded; the large-transaction and large-cash rules take the local cash reporting threshold). In any other currency the cedi figures are installed as they are: set them for your currency before enabling.

Version 1.1.0 added C6, fraud.unusual_hour. Version 1.2.0 makes C6 compare like with like, so it no longer over-fires after the upgrade that introduced hour counters (see below).

Version 1.3.0 states that the pack’s amounts are in Ghana cedis (GHS) and adds equivalent starting amounts for workspaces whose base currency is NGN, XOF, USD or EUR; no rule’s logic changed. The values in each rule’s section below are the cedi defaults. In a currency listed in this table, installing or upgrading uses its amounts instead; in any other currency the cedi figures are installed unchanged and the pack’s row warns you. They are round starting points, not precise conversions: calibrate them before go-live. See Packs written in another currency.

Version 1.4.0 adds starting amounts for five more currencies: SLE, LRD, GMD, GNF and KES. They are the US dollar figures scaled at a rough exchange rate and rounded, so treat them as unverified starting points and review them for your customers before you enable a rule. No rule’s logic changed.

Default amounts by currency
Rule Parameter GHS (as written) NGN XOF USD EUR SLE LRD GMD GNF KES
fraud.card_testing small_amount 20 2,500 1,000 2 2 50 400 150 15,000 250
fraud.new_device_new_payee threshold 2,000 250,000 100,000 150 150 3,000 30,000 10,000 1,250,000 20,000
fraud.profile_change_then_payout min_amount 500 60,000 25,000 40 40 1,000 8,000 3,000 300,000 5,000
fraud.new_country min_amount 1,000 125,000 50,000 80 80 2,000 15,000 6,000 750,000 10,000
fraud.payout_burst min_total 1,000 125,000 50,000 80 80 2,000 15,000 6,000 750,000 10,000
fraud.unusual_hour min_amount 1,000 125,000 50,000 80 80 2,000 15,000 6,000 750,000 10,000

Card testing: many small card payments · library H1 · Card testing · real-time, answers block · fraud alert about the customer · severity high, score 80

Fires on a card transaction when the customer has made at least max_auths outgoing card payments in the last 10 minutes, this one included, and their average is at most small_amount. merchants_10m is only shown in the explanation.

Parameter Meaning Default
max_auths Card payments in 10 minutes 5
small_amount Average amount at or below (base currency) 20

Features: card_10m (customer, count, out, card, 10 minutes); card_avg_10m (customer, avg, out, card, 10 minutes); merchants_10m (customer, distinct_counterparties, out, 10 minutes).

Explanation shown to analysts: {{features.card_10m}} card payments averaging {{txn.base_currency}} {{features.card_avg_10m}} in 10 minutes, at {{features.merchants_10m}} merchants.

Expression
txn.channel == 'card' && features.card_10m >= double(params.max_auths) && features.card_avg_10m <= params.small_amount

New device paying a new payee a large amount · library H2 · Account takeover · real-time, answers review · fraud alert about the customer · severity high, score 70

Fires on an outgoing transaction of at least threshold that is both the customer’s first from this device and their first with this counterparty.

Parameter Meaning Default
threshold Amount at or above (base currency) 2,000

Needs device_fingerprint and a counterparty on the transaction. See What “new” means.

Explanation shown to analysts: First payment from this device and first payment to this payee: {{txn.base_currency}} {{txn.amount}} out.

Expression
txn.direction == 'out' && device.known && device.is_new && txn.has_counterparty && counterparty.is_new_for_customer && txn.amount >= params.threshold

Payout after repeated failed OTP or PIN · library H3 · Account takeover · real-time, answers review · fraud alert about the customer · severity high, score 65

Fires on an outgoing transaction when the customer had at least max_failures failed OTPs and failed PINs, added together, in the last 30 minutes. It doesn’t wait for a successful attempt.

Parameter Meaning Default
max_failures Failed OTPs and PINs in 30 minutes 3

Features: otp_failed_30m (customer, count, otp_failed events, 30 minutes); pin_failed_30m (customer, count, pin_failed events, 30 minutes).

Needs otp_failed and pin_failed account events.

Explanation shown to analysts: {{features.otp_failed_30m}} failed OTPs and {{features.pin_failed_30m}} failed PINs in the last 30 minutes before this payout.

Expression
txn.direction == 'out' && features.otp_failed_30m + features.pin_failed_30m >= double(params.max_failures)

Payout soon after a profile or credential change · library H5 · Account takeover · real-time, answers review · fraud alert about the customer · severity high, score 70

Fires on an outgoing transaction of at least min_amount when any event in change_events happened less than window_hours hours before it.

Parameter Meaning Default
window_hours Hours since the change 24
min_amount Payout at or above (base currency) 500
change_events Changes that count ["phone_change", "email_change", "password_reset", "password_change", "device_added"]

Needs account events of the types in change_events. The explanation lists every change in change_events that happened within the window, with how long ago (in minutes under an hour, otherwise in whole hours), for example Payout of GHS 2,400 after phone change 20h ago, email change 35m ago (rule window: 24 hours). Changes the rule doesn’t watch, such as a SIM swap, aren’t listed. It uses a computed placeholder.

Explanation shown to analysts:

Payout of {{txn.base_currency}} {{txn.amount}} after {{= params.change_events.filter(e, e in customer.hours_since && customer.hours_since[e] < double(params.window_hours)).map(e, e.replace('_', ' ') + ' ' + (customer.hours_since[e] < 1.0 ? string(int(customer.hours_since[e] * 60.0)) + 'm ago' : string(int(customer.hours_since[e])) + 'h ago')).join(', ') }} (rule window: {{params.window_hours}} hours).
Expression
txn.direction == 'out' && txn.amount >= params.min_amount && params.change_events.exists(e, e in customer.hours_since && customer.hours_since[e] < params.window_hours)

First transaction in or to a new country · library E2 · Unusual geography · real-time, answers step-up · fraud alert about the customer · severity medium, score 40

Fires on a transaction of at least min_amount, in either direction, that involves a country this customer has never transacted with, when they have more than min_history transactions in the last 90 days, this one included. The country is the counterparty’s when sent, otherwise the transaction’s.

Parameter Meaning Default
min_amount Amount at or above (base currency) 1,000
min_history Earlier transactions (90 days) before it counts as a change 10

Features: txns_90d (customer, count, 90 days).

Needs counterparty_country or country. Every country is new for a customer’s first transactions, which is why min_history exists.

Explanation shown to analysts: First transaction involving this country for a customer with {{features.txns_90d}} transactions in 90 days.

Expression
txn.country_is_new && txn.amount >= params.min_amount && features.txns_90d > double(params.min_history)

Impossible travel · library E3 · Account takeover / cloned credentials · real-time, answers review · fraud alert about the customer · severity high, score 70

Fires when the transaction carries a location more than min_km km from the customer’s previous known location, and getting there in the time between would need more than max_kmh km/h.

Parameter Meaning Default
max_kmh Speed above (km/h) 900
min_km Distance above (km) 100

Needs geo_lat and geo_lon on transactions. A transaction dated at or before the latest known location has no speed (-1), so it never fires this rule; see What “new” means.

Explanation shown to analysts: {{txn.km_from_last}} km from the previous location in {{txn.minutes_since_last_location}} minutes ({{txn.kmh_from_last}} km/h).

Expression
txn.has_location && txn.km_from_last > params.min_km && txn.kmh_from_last > params.max_kmh

Device shared by many customers · library D5 · Mule network / account farm · real-time, answers review · fraud alert about the customer · severity high, score 60

Fires when the transaction’s device has been used by more than max_customers different customers in 30 days, counting transactions and account events that carried its fingerprint.

Parameter Meaning Default
max_customers Customers per device in 30 days, above 3

Features: device_customers_30d (device, distinct_customers, 30 days).

Needs device_fingerprint. Family members sharing a phone, and agents’ devices, are the usual false positives.

Explanation shown to analysts: This device has been used by {{features.device_customers_30d}} customers in 30 days.

Expression
device.known && features.device_customers_30d > double(params.max_customers)

Burst of payouts · library C7 · Account takeover / mule cash-out · real-time, answers step-up · fraud alert about the customer · severity medium, score 50

Fires on an outgoing transaction when the customer has made at least max_payouts outgoing transactions in the last 10 minutes, this one included, totalling at least min_total.

Parameter Meaning Default
max_payouts Payouts in 10 minutes 5
min_total Total at or above (base currency) 1,000

Features: out_10m (customer, count, out, 10 minutes); out_sum_10m (customer, sum, out, 10 minutes).

Explanation shown to analysts: {{features.out_10m}} payouts totalling {{txn.base_currency}} {{features.out_sum_10m}} in 10 minutes.

Expression
txn.direction == 'out' && features.out_10m >= double(params.max_payouts) && features.out_sum_10m >= params.min_total

Sanctioned customer or counterparty · library F1-RT · Sanctions · real-time, answers block · fraud alert about the customer · severity critical, score 100

Fires on any transaction where the customer or the counterparty carries a current sanctioned designation. The logic is the same as core.sanctioned_party, but in a real-time decision it blocks the payment before it happens; the post-event rule still raises the AML alert.

No parameters.

Explanation shown to analysts: A party to this transaction is designated sanctioned.

Expression
customer.is_sanctioned || 'sanctioned' in counterparty.designations

Counterparty on your deny list · library F4 · Deny list · real-time, answers block · fraud alert about the customer · severity high, score 90

Fires when the counterparty’s country is on the blocked_countries list, or the merchant category code is on the blocked_mcc list. Despite its name, it doesn’t check named counterparties: put a person or wallet you want stopped under a sanctioned designation instead.

No parameters.

Needs the lists blocked_countries or blocked_mcc.

Explanation shown to analysts: The counterparty country or merchant category is on lists.blocked_countries or lists.blocked_mcc.

Expression
(txn.counterparty_country != '' && txn.counterparty_country in lists.blocked_countries) || (txn.mcc != '' && txn.mcc in lists.blocked_mcc)

Payout at an hour this customer never uses · library C6 · Account takeover · real-time, answers step-up · fraud alert about the customer · severity medium, score 40

Fires on an outgoing payment of at least min_amount by a customer with at least min_history transactions in the last 90 days, when no more than max_prior_at_hour of those were in the same clock hour as this one (this one not counted).

Parameter Meaning Default
min_history Transactions in 90 days before it applies 30
max_prior_at_hour Earlier transactions at this hour, at most 0
min_amount Payout at or above (base currency) 1,000

Features: hour_counted_90d (customer, count, every transaction the hour counters have seen, 90 days), same_hour_90d (customer, count, same hour of the day in the workspace’s time zone, 90 days).

History is measured with the hour counters themselves, so the rule doesn’t apply until they hold min_history of the customer’s transactions (version 1.2.0; 1.1.0 compared with the full 90-day count and over-fired after the upgrade).

Explanation shown to analysts:

{{txn.hour}}:00 is unusual for this customer: {{= string(int(features.same_hour_90d) - 1) }} of their {{features.hour_counted_90d}} transactions in 90 days were at this hour. Payout of {{txn.base_currency}} {{txn.amount}}.
Expression
txn.direction == 'out' && txn.amount >= params.min_amount && features.hour_counted_90d >= double(params.min_history) && features.same_hour_90d - 1.0 <= double(params.max_prior_at_hour)

Typologies for Ghanaian payment service providers, mobile money operators and agent networks. Calibrate thresholds against your own data and current Bank of Ghana / FIC guidance.

Version 1.1.0 added the two real-time rules, G3 and A4-RT. Version 1.2.0 added E4 (post-event) and G1, I1, I2 and I3 (scheduled); the last four of those are shared with gh-bank.

Payout soon after a SIM swap · library G3 · SIM swap · real-time, answers block · fraud alert about the customer · severity critical, score 85

Fires on an outgoing transaction of at least threshold when the customer’s latest sim_swap account event happened less than sim_change_hours hours before it. A customer with no SIM swap on record never matches.

Parameter Meaning Default
sim_change_hours Hours since the SIM swap 48
threshold Payout at or above (base currency) 200

Needs sim_swap account events. The explanation gives the actual hours since the swap, with the rule’s window for comparison.

Explanation shown to analysts: SIM swapped {{customer.hours_since.sim_swap}} hours before this payout of {{txn.base_currency}} {{txn.amount}} (rule window: {{params.sim_change_hours}} hours).

Expression
txn.direction == 'out' && txn.amount >= params.threshold && 'sim_swap' in customer.hours_since && customer.hours_since.sim_swap < params.sim_change_hours

Decline above KYC tier limit · library A4-RT · Tier limit breach · real-time, answers block · fraud alert about the customer · severity medium, score 50

Fires on an outgoing transaction above the single-transaction limit for the customer’s KYC tier. Customers whose tier is unknown, or not listed in limits, are skipped. It is the real-time partner of gh.kyc_tier_single_limit, which also checks incoming money but can only alert.

Parameter Meaning Default
limits Single-transaction limit by KYC tier (base currency) {"1": 1000, "2": 5000, "3": 20000}

Explanation shown to analysts: {{txn.base_currency}} {{txn.amount}} exceeds the single-transaction limit for this customer’s KYC tier.

Expression
txn.direction == 'out' && string(customer.kyc_level) in params.limits && txn.amount > double(params.limits[string(customer.kyc_level)])

Transaction above KYC tier limit · library A4 · Tier limit breach · alert about the customer · severity medium, score 45

Fires when a transaction is above the single-transaction limit for the customer’s KYC tier. Customers whose tier is unknown, or not listed in limits, are skipped.

Parameter Meaning Default
limits Single-transaction limit by KYC tier (base currency) {"1": 1000, "2": 5000, "3": 20000}

Explanation shown to analysts: {{txn.base_currency}} {{txn.amount}} exceeds the single-transaction limit for this customer’s KYC tier.

Expression
string(customer.kyc_level) in params.limits && txn.amount > double(params.limits[string(customer.kyc_level)])

Daily outflow above KYC tier limit · library A5 · Tier limit breach · alert about the customer · severity medium, score 45

Fires on an outflow when the customer’s outflows in the last 24 hours, across all their accounts, are above the daily limit for their KYC tier.

Parameter Meaning Default
limits 24h outflow limit by KYC tier (base currency) {"1": 2000, "2": 10000, "3": 50000}

Features: out_24h (customer, sum, out, 24 hours).

Explanation shown to analysts: Outflows of {{txn.base_currency}} {{features.out_24h}} in 24h exceed the daily limit for this customer’s KYC tier.

Expression
txn.direction == 'out' && string(customer.kyc_level) in params.limits && features.out_24h > double(params.limits[string(customer.kyc_level)])

Cash-in split across many agents · library B2 · Smurfing through agents · alert about the customer · severity high, score 70

Fires on a cash-in when the account has received money from at least min_agents different parties in 24 hours and its cash-ins in those 24 hours add up to at least min_total.

Parameter Meaning Default
min_agents Distinct agents in 24h 4
min_total Total cash-in in 24h (base currency) 10,000

Features: agents_24h (account, distinct counterparties, in, 24 hours); cash_in_24h (account, sum, in, type cash_in, 24 hours).

Explanation shown to analysts: Cash-ins via {{features.agents_24h}} different agents totalling {{txn.base_currency}} {{features.cash_in_24h}} in 24h.

Expression
txn.type == 'cash_in' && features.agents_24h >= double(params.min_agents) && features.cash_in_24h >= params.min_total

Cash-out right after cash-in · library G2 · Wallet used as pass-through · alert about the customer · severity high, score 60

Fires on a cash-out when the account cashed in at least min_amount in the last 2 hours and has cashed out at least ratio of it in the same 2 hours.

Parameter Meaning Default
min_amount Minimum cash-in (base currency) 3,000
ratio Cash-out as share of cash-in 0.8

Features: cash_in_2h (account, sum, in, type cash_in, 2 hours); cash_out_2h (account, sum, out, type cash_out, 2 hours).

Explanation shown to analysts: {{txn.base_currency}} {{features.cash_in_2h}} cashed in and {{txn.base_currency}} {{features.cash_out_2h}} cashed out within 2 hours.

Expression
txn.type == 'cash_out' && features.cash_in_2h >= params.min_amount && features.cash_out_2h >= params.ratio * features.cash_in_2h

Wallet swept to bank after many inbound payments · library G4 · Collection wallet · alert about the customer · severity high, score 60

Fires on a bank transfer or instant payment out of the account when at least min_senders different senders paid it in 7 days and this payment is at least sweep_ratio of its 7-day inflow.

Parameter Meaning Default
min_senders Distinct senders in 7 days 10
sweep_ratio Transfer as share of 7-day inflow 0.7

Features: senders_7d (account, distinct counterparties, in, 7 days); in_7d (account, sum, in, 7 days).

Explanation shown to analysts: {{features.senders_7d}} senders paid in {{txn.base_currency}} {{features.in_7d}} over 7 days; {{txn.base_currency}} {{txn.amount}} is now leaving to a bank.

Expression
txn.direction == 'out' && txn.channel in ['bank_transfer', 'instant_payment'] && features.senders_7d >= double(params.min_senders) && txn.amount >= params.sweep_ratio * features.in_7d

Repeated reversals or refunds · library G5 · Reversal fraud · alert about the customer · severity medium, score 45

Fires on a reversal when the customer has had at least max_reversals reversals (including this one) in 30 days.

Parameter Meaning Default
max_reversals Reversals in 30 days 5

Features: reversals_30d (customer, count, type reversal, 30 days).

Explanation shown to analysts: {{features.reversals_30d}} reversals in 30 days.

Expression
txn.type == 'reversal' && features.reversals_30d >= double(params.max_reversals)

Large mobile money transfer at night · library G6 · Account takeover / social engineering · alert about the customer · severity medium, score 40

Fires on a mobile money outflow of at least threshold made between from_hour and to_hour (inclusive, workspace time).

Parameter Meaning Default
threshold Threshold (base currency) 5,000
from_hour Night starts (hour) 0
to_hour Night ends (hour) 5

Explanation shown to analysts: {{txn.base_currency}} {{txn.amount}} sent by mobile money at {{txn.hour}}:00.

Expression
txn.channel == 'momo' && txn.direction == 'out' && txn.amount >= params.threshold && txn.hour >= int(params.from_hour) && txn.hour <= int(params.to_hour)

Agent cash-in volume far above peer agents · library G1 · Agent misuse / cash placement · scheduled, daily 05:00 · alert about the agent (its account, or the agent as a counterparty) · severity medium, score 55

Over the last window_days, totals the cash-ins at each agent (the counterparty of your customers’ cash_in transactions) and compares each agent with the others in its peer group, read from the attribute named in peer_group on the agent’s own account, then that account’s customer, then the cash-in itself (all when there is none). Fires when an agent’s volume is at least min_volume and at least zscore standard deviations above the average of at least min_peers other agents, its own volume left out of the average. The alert is about the agent’s account when the agent is one of your accounts, otherwise about the agent as a counterparty.

Parameter Meaning Default
window_days Window (days) 7
zscore Standard deviations above the peers, at least 3
min_peers Other agents in the peer group, at least 10
min_volume Cash-in volume at or above (base currency) 50,000
peer_group Peer group attribute (e.g. region or tier) "region"

Each hit records volume, cash_ins, peer_mean, peer_sd, zscore, peer_count for the explanation, as {{features.<name>}}, and peer_group as {{subject.<name>}}.

Explanation shown to analysts: Customers cashed in {{txn.base_currency}} {{features.volume}} at this agent in {{params.window_days}} days ({{features.cash_ins}} cash-ins): {{features.zscore}} standard deviations above the {{features.peer_count}} other agents in {{subject.peer_group}}, who average {{txn.base_currency}} {{features.peer_mean}}.

Runs the built-in scenario peer_volume_anomaly (scenario:peer_volume_anomaly) on the schedule daily 05:00, in your workspace’s time zone. See Scheduled rules.

Merchant takings far above declared turnover · library I1 · Front business / merchant laundering · scheduled, daily 04:30 · alert about the customer · severity medium, score 55

Runs the same check as core.declared_profile, tuned for businesses and money in: over the last period_days, a business customer’s takings scaled to 30 days are more than multiplier times its declared monthly turnover, and at least min_volume. With occupation_caps empty, customers without a declared turnover in your base currency are skipped.

Parameter Meaning Default
period_days Period (days) 30
multiplier Times the declared monthly turnover 2
min_volume Takings at or above (base currency) 20,000
direction Count money in, out, or both "in"
customer_types Customer types ["business"]
segments Segments (empty = all) []
occupation_caps Expected monthly turnover by occupation, when none was declared (base currency) {}

Each hit records volume, monthly_volume, declared, ratio for the explanation, as {{features.<name>}}.

Explanation shown to analysts: Received {{txn.base_currency}} {{features.volume}} in {{params.period_days}} days (about {{txn.base_currency}} {{features.monthly_volume}} a month): {{features.ratio}}× the declared monthly turnover of {{txn.base_currency}} {{features.declared}}.

Runs the built-in scenario declared_volume (scenario:declared_volume) on the schedule daily 04:30, in your workspace’s time zone. See Scheduled rules.

Merchant with many refunds or chargebacks · library I2 · Refund / chargeback abuse · scheduled, daily 05:30 · alert about the customer · severity medium, score 50

Over the last period_days, counts each merchant’s sales (incoming payment and transfer), refunds (outgoing refund) and chargebacks (every chargeback). Fires when it had at least min_sales sales and refunds plus chargebacks are more than max_ratio of them, by count. A merchant is a business customer, or any customer with an account of type merchant.

Parameter Meaning Default
period_days Period (days) 30
max_ratio Refunds and chargebacks per sale, above (0.1 = 10%) 0.1
min_sales Sales in the period, at least 50

Each hit records sales, sales_amount, refunds, chargebacks, refund_amount, ratio for the explanation, as {{features.<name>}}.

Explanation shown to analysts: {{features.refunds}} refunds and {{features.chargebacks}} chargebacks against {{features.sales}} sales in {{params.period_days}} days (ratio {{features.ratio}}; {{txn.base_currency}} {{features.refund_amount}} paid back).

Runs the built-in scenario refund_ratio (scenario:refund_ratio) on the schedule daily 05:30, in your workspace’s time zone. See Scheduled rules.

Personal account trading like a business · library I3 · Undeclared business / collection account · scheduled, daily 04:45 · alert about the customer · severity medium, score 45

Over the last period_days, fires on an individual customer who received incoming transfer, payment or deposit transactions from at least min_distinct_payers different counterparties, totalling at least min_inflow. Cash-ins aren’t counted.

Parameter Meaning Default
period_days Period (days) 30
min_distinct_payers Distinct payers, at least 40
min_inflow Inflow at or above (base currency) 20,000

Each hit records payers, payments, inflow for the explanation, as {{features.<name>}}.

Explanation shown to analysts: {{features.payers}} different payers made {{features.payments}} payments totalling {{txn.base_currency}} {{features.inflow}} into this personal account in {{params.period_days}} days.

Runs the built-in scenario distinct_payers (scenario:distinct_payers) on the schedule daily 04:45, in your workspace’s time zone. See Scheduled rules.

Activity in a border or high-risk area · library E4 · Border / cash courier risk · alert about the customer · severity medium, score 40

Fires on a transaction of at least min_amount, on one of channels (empty means all), whose region, district, town or branch_region attribute names an area in regions or on the workspace list high_risk_regions, ignoring case.

Parameter Meaning Default
regions Areas (towns, districts or regions) ["Aflao", "Akanu", "Kpetoe", "Elubo", "Sampa", "Paga", "Hamile", "Kulungugu", "Tumu", "Bawku", "Nkwanta"]
channels Only these channels (empty = all) ["cash", "agent", "branch", "momo"]
min_amount Amount at or above (base currency) 5,000

The default areas are illustrative crossing points, not a regulatory list. Replace them with your own risk assessment.

Explanation shown to analysts:

{{txn.base_currency}} {{txn.amount}} by {{txn.channel}} in {{= ['region', 'district', 'town', 'branch_region'].filter(k, k in txn.attributes).map(k, string(txn.attributes[k])).join(', ') }}, a border or high-risk area.
Expression
txn.amount >= params.min_amount && (size(params.channels) == 0 || txn.channel in params.channels) && ['region', 'district', 'town', 'branch_region'].exists(k, k in txn.attributes && (params.regions.exists(r, r.lowerAscii() == string(txn.attributes[k]).lowerAscii()) || lists.high_risk_regions.exists(r, r.lowerAscii() == string(txn.attributes[k]).lowerAscii())))

Typologies for Ghanaian universal and rural banks: branch cash and structuring across branches, foreign-currency cash, cheques, GhIPSS instant (GIP) transfers, trade payments and correspondent exposure, plus the merchant and border-area rules shared with gh-psp. Thresholds are starting points: calibrate them against your own data, and confirm every reporting threshold with current Bank of Ghana and FIC guidance before go-live.

Version 1.1.0 lets GB2 read amounts with each currency’s own decimals and adds an optional base-currency threshold, and makes GB5 match the correspondent list ignoring capitals.

Cash deposits split across branches · library GB1 · Structuring across branches · scheduled, hourly :20 · alert about the customer · severity high, score 75

Over the last window_hours, finds customers who made at least min_deposits cash deposits (deposit or cash_in through branch or cash), each between margin below threshold and just under it, at at least min_branches different branches, together at least threshold. The branch is the deposit’s branch or branch_code attribute; deposits without one aren’t counted.

Parameter Meaning Default
window_hours Window (hours) 48
threshold Reporting threshold being avoided (base currency) 50,000
margin Just below = within (0.15 = 15%) of the threshold 0.15
min_branches Different branches, at least 2
min_deposits Deposits, at least 3

Each hit records deposits, branches, total, largest for the explanation, as {{features.<name>}}, and branch_list as {{subject.<name>}}.

The GHS 50,000 default mirrors the FIC cash threshold as we understand it. Confirm it with the FIC.

Explanation shown to analysts: {{features.deposits}} cash deposits just below {{txn.base_currency}} {{params.threshold}} at {{features.branches}} branches ({{subject.branch_list}}) within {{params.window_hours}} hours, together {{txn.base_currency}} {{features.total}} (largest {{txn.base_currency}} {{features.largest}}).

Runs the built-in scenario branch_structuring (scenario:branch_structuring) on the schedule hourly :20, in your workspace’s time zone. See Scheduled rules.

Large foreign-currency cash · library GB2 · Foreign-currency cash reporting · alert about the customer · severity medium, score 40

Fires on a cash deposit, withdrawal, cash_in or cash_out through cash, branch or atm in one of currencies (never your base currency) when the amount in that currency, with that currency’s own decimals, is at least threshold, or, when threshold_base is above 0, when its base-currency value is at least threshold_base.

Parameter Meaning Default
threshold Threshold in the transaction’s currency (major units, with that currency’s decimals) 10,000
threshold_base Or at or above this in the base currency (0: off) 0
currencies Currencies ["USD", "EUR", "GBP"]

The 10,000 default applies to each currency as is; set threshold_base for an “or its equivalent” threshold. Confirm the threshold, and whether it is per currency or a US-dollar equivalent, with the FIC.

Explanation shown to analysts: Cash {{txn.type}} in {{txn.currency}} worth {{txn.base_currency}} {{txn.amount}}: at or above the {{params.threshold}} foreign-currency cash threshold. Confirm the threshold with current FIC guidance.

Expression
txn.currency in params.currencies && txn.currency != txn.base_currency && txn.type in ['deposit', 'withdrawal', 'cash_in', 'cash_out'] && txn.channel in ['cash', 'branch', 'atm'] && (txn.amount_original >= params.threshold || (params.threshold_base > 0.0 && txn.amount >= params.threshold_base))

Cheque credits withdrawn in cash · library GB3 · Cheque fraud / kiting · alert about the account · severity high, score 65

Fires on an outgoing withdrawal or cash_out when at least min_cheques arrived on the account through the cheque channel in the last 48 hours and the withdrawal is at least ratio of it. The alert is about the account.

Parameter Meaning Default
min_cheques Cheque credits in 48 hours, at least (base currency) 20,000
ratio Cash withdrawn as share of the cheque credits 0.7

Features: cheque_in_48h (account, sum, in, channel cheque, 2 days).

Explanation shown to analysts: {{txn.base_currency}} {{features.cheque_in_48h}} in cheques was credited in 48 hours and {{txn.base_currency}} {{txn.amount}} is now withdrawn in cash.

Expression
txn.direction == 'out' && txn.type in ['withdrawal', 'cash_out'] && features.cheque_in_48h >= params.min_cheques && txn.amount >= params.ratio * features.cheque_in_48h

Large instant transfer to a new payee · library GB4 · Authorised push-payment fraud · alert about the customer · severity medium, score 50

Fires on an outgoing instant_payment (GhIPSS GIP) of at least threshold to a counterparty the customer has never dealt with before.

Parameter Meaning Default
threshold Amount at or above (base currency) 20,000

Explanation shown to analysts: First instant transfer to this beneficiary: {{txn.base_currency}} {{txn.amount}}.

Expression
txn.direction == 'out' && txn.channel == 'instant_payment' && txn.has_counterparty && counterparty.is_new_for_customer && txn.amount >= params.threshold

Cross-border flow through a high-risk correspondent · library GB5 · Correspondent banking exposure · alert about the customer · severity high, score 60

Fires on a transaction of at least threshold, in either direction, with a counterparty in a country other than home_country whose provider is on the workspace list high_risk_correspondents.

Parameter Meaning Default
home_country Home country (ISO code) "GH"
threshold Amount at or above (base currency) 10,000

List entries are matched ignoring capitals and surrounding spaces (since 1.1.0). Maintaining the list is your institution’s responsibility; confirm the expected scope with the Bank of Ghana.

Explanation shown to analysts: {{txn.base_currency}} {{txn.amount}} {{txn.direction}} with {{txn.counterparty_country}} through {{counterparty.provider}}, on the workspace’s high-risk correspondent list.

Expression
txn.counterparty_country != '' && txn.counterparty_country != params.home_country && counterparty.provider != '' && lists.high_risk_correspondents.exists(c, c.trim().lowerAscii() == counterparty.provider.lowerAscii()) && txn.amount >= params.threshold

Trade payments abroad outside the customer’s line of business · library I4 · Trade-based money laundering · scheduled, daily 05:15 · alert about the customer · severity medium, score 55

Over the last window_days, takes each business customer’s completed payments abroad (country other than home_country) of at least threshold_amount that carry a sector or isic attribute. The customer’s industry_code picks the longest matching key in sector_map; a payment whose sector doesn’t start with one of that key’s values is a mismatch. Fires when a customer’s mismatched payments total at least min_total. Customers whose industry isn’t in the map, and payments without a sector, aren’t judged.

Parameter Meaning Default
window_days Window (days) 7
home_country Home country (ISO code) "GH"
threshold_amount Payments at or above (base currency) 50,000
min_total Total at or above (base currency) 100,000
sector_map Customer industry (ISIC prefix) → sectors it plausibly pays (ISIC prefixes) Eight industries, below

Default sector_map, customer industry → sectors it plausibly pays:

{
"01": ["01", "02", "10", "20", "28", "46", "49", "52"],
"10": ["01", "03", "10", "11", "17", "20", "22", "28", "46", "49", "52"],
"41": ["16", "22", "23", "24", "25", "27", "28", "41", "42", "43", "46", "49", "71", "77"],
"42": ["16", "22", "23", "24", "25", "27", "28", "41", "42", "43", "46", "49", "71", "77"],
"46": ["10", "11", "13", "14", "15", "17", "20", "21", "22", "23", "25", "26", "27", "28", "29", "46", "49", "50", "52"],
"47": ["10", "13", "14", "15", "20", "21", "26", "27", "46", "47", "49", "52"],
"62": ["26", "58", "61", "62", "63", "70", "72"],
"86": ["21", "26", "32", "46", "86"]
}

Each hit records payments, amount, countries for the explanation, as {{features.<name>}}, and industry, sectors, countries_list as {{subject.<name>}}.

The default map is a short illustration covering eight industries. Extend it for your book before relying on this rule.

Explanation shown to analysts: {{features.payments}} payments abroad totalling {{txn.base_currency}} {{features.amount}} in {{params.window_days}} days to sector(s) {{subject.sectors}} in {{subject.countries_list}}, outside what the sector map expects for the customer’s industry ({{subject.industry}}).

Runs the built-in scenario trade_sector_mismatch (scenario:trade_sector_mismatch) on the schedule daily 05:15, in your workspace’s time zone. See Scheduled rules.

Also in this pack, described above under gh-psp:

Key Library Name
gh.merchant_turnover I1 Merchant takings far above declared turnover
gh.merchant_refund_ratio I2 Merchant with many refunds or chargebacks
gh.personal_business_volume I3 Personal account trading like a business
gh.border_region E4 Activity in a border or high-risk area