Every rules engine has the same blind spot. It can only catch attacks that someone wrote a rule for. The rule that blocks transfers above a threshold was written after someone lost money above that threshold. The rule that flags a new country was written after the first incident from that country. Rules are a memory of fraud, and fraud has no obligation to repeat itself.
The attacks that hurt institutions most are the ones where every rule passes. A stolen session token is a valid credential. An authorized push payment scam produces a transfer the customer genuinely approves. A mule account behaves perfectly for weeks, because behaving perfectly is the plan. Row-by-row rule evaluation sees nothing wrong, because nothing wrong was ever described to it.
This is the gap our behavioral trust score exists to close. It does not ask whether a transaction matches a known-bad pattern. It asks whether the account is still behaving like itself.
The baseline is the account's own history
Most fraud systems score behavior against a global average: typical transaction size for the segment, typical login hour for the region, typical device class for the customer tier. Global averages are a weak yardstick. A night-shift nurse and a day trader have nothing in common, and scoring both against the same "normal" produces false alarms for one and blind spots for the other.
So every account on Finhaq builds its own reference distribution, and only its own history is the yardstick. The baseline covers five families:
- Devices. The set of device fingerprints the account has authenticated from, with first-seen and last-seen timestamps.
- Locations and networks. The IP ranges, ASNs, and geographies the account connects from, including whether a network is residential, mobile, or a datacenter.
- Velocity. How many transactions the account initiates per hour and per day, and the amounts those transactions typically carry.
- Typical hours. The account's activity histogram across the day, in its own timezone.
- Counterparties. Who the account sends money to and receives money from, and how long those relationships have existed.
The baseline is not static. It updates continuously as the account lives its life, so a customer who starts traveling, switches phones, or changes jobs drifts their baseline along with them. What the system measures is deviation from this account's average, not humanity's. That is much harder to imitate, because the attacker has the credentials but not the history.
A score that moves on evidence, not suspicion
Every account carries a trust score from 0 to 100. It starts from KYC tier and verification depth, then moves as events arrive. The movement is the whole design: scores change on evidence, meaning concrete, logged, explainable signals, and never on vibes. Every point deducted can be traced to a specific observation with a timestamp.
Here is the signal ledger for a real session, the one from the incident we referenced earlier in this series:
# trust score ledger, account 7F3A...C9, 2026-09-14 03:12 WAT
# starting score: 78 (aged account, verified KYC, stable baseline)
multiple devices in one session -22 # device family
IP belongs to a datacenter -18 # network family
night time requests (03:12 local) -12 # temporal family
3 shared IPs with flagged accounts -30 # identity family
# 78 - 82 = score would floor at 0; contribution caps apply
# effective score after caps: 18 < threshold 20
Two details matter here. First, every signal is a sentence a compliance officer can say out loud to a regulator. There is no "model output: suspicious." Second, recovery is asymmetric by design. Positive history restores the score, but slowly, and old signals decay on a schedule rather than vanishing at once. A clean week of normal behavior earns back a few points per day. This is deliberate, and it is the next section.
Design rule: a trust score is a ledger, not a label. If you cannot point at the exact observations that moved it, you do not have a score, you have a hunch with a number attached.
Trust is fast to lose and slow to regain
The asymmetry is the correct security posture, and it is worth stating plainly why. The cost of a false negative is catastrophic and irreversible: funds leave, convert, and are gone. The cost of a false positive is a customer completing a step-up verification or waiting a few hours for a review. One side of that trade is recoverable and the other is not, so the system leans hard toward the recoverable side.
Concretely: one bad session can cost 40 points in ninety seconds. Earning those 40 points back takes weeks of ordinary behavior. An attacker who steals credentials inherits the password but not the patience. Holding a compromised account quietly for six weeks while it rebuilds trust is operationally ruinous for a fraud ring, which runs on volume and speed.
The honest price of this posture is false positives. Legitimate customers do occasionally trip signals: a new phone, a hotel network, a late night. The mitigation is not loosening the score, it is making the responses proportionate. The lowest tier of response is an extra verification step, not a lock. Friction scales with evidence, and only the bottom tier freezes anything at all.
Thresholds are actions, not warnings
A score that nobody acts on is a dashboard decoration. Ours is wired directly into the decision path, with three hard thresholds:
- Around 60: step-up verification. The transaction proceeds only after an additional proof: a biometric check, a hardware key, a confirmed push to a known device. The customer experiences friction, not a wall.
- Around 40: review queue. The transaction is held and a case is opened automatically with the signal ledger attached. An officer reviews it, typically within the hour. The customer sees a pending state.
- Below 20: auto-interdiction. The account freezes itself. No human is in the loop for the freeze, because no human is awake at 03:12 and the money does not wait for one.
Here is exactly what executes when a score crosses 20, told through the incident from the opening of this series. At 03:12 WAT, account 7F3A...C9 initiated a TRANSFER of ₦4,250,000 from a device it had never used, on a datacenter IP, sharing three IPs with accounts already flagged across the network. The six screening engines returned their verdict in 155 milliseconds. Inside that same request path, the behavioral engine wrote four signals to the ledger, the score dropped from 78 to 18, and the threshold crossed.
Crossing the threshold fires a deterministic sequence:
on trust_score_crossed(account, threshold=20):
freeze(account, direction=BOTH) # debits and credits stop
block_pending(account) # the ₦4,250,000 never settles
case = open_case(account, priority=P1)
case.attach(signal_ledger) # every point, with timestamps
case.attach(session_context) # devices, IPs, timing
trace = start_fund_trace(account) # hands off to the tracer
notify(officers_on_call, case, trace)
audit.append(event, actor=SYSTEM, reversible=TRUE)
That night, the transfer was blocked in 155ms, the account froze itself, a case opened with the evidence already attached, and the tracer took over from there, eventually freezing ₦4.1 million at the third hop. The officers on call were paged with a case they could read in two minutes, because the ledger is the explanation. No log diving, no reconstruction. The first human decision of the morning was whether to keep the freeze, not whether to impose one.
Guardrails that keep the automation honest
Software that freezes accounts autonomously needs harder limits than software that recommends. We enforce four:
- No auto-freeze from a single signal family. Reaching the interdiction threshold requires evidence from at least two independent families, for example device plus network, or temporal plus identity. A customer whose phone breaks should be inconvenienced by step-up checks, never frozen by one unlucky signal cluster.
- Contribution caps. Each signal family has a maximum total deduction, so no single dimension can dominate the score. The ledger above shows the caps binding: the raw deduction was 82 points, the effective drop was 60.
- Every freeze is reversible by an officer, with a full audit trail. Unfreezing is one action, it takes effect immediately, and both the freeze and the unfreeze are recorded with actor, timestamp, and the complete ledger state at the moment of decision. Automation acts fast; humans hold the override.
- Drift monitoring on the baselines themselves. If a baseline model drifts, for example after a product change alters how customers authenticate, we detect the shift in population-level score distributions and flag it before it manufactures false interdictions at scale.
These limits mean the system's worst-case failure mode is friction, not injustice. We would rather explain a step-up challenge to an annoyed customer than explain an unrecoverable freeze to an examiner.
Closing the loop
This series has followed one incident through four layers. In Part 2, events became verdicts: six engines evaluating the ₦4,250,000 transfer in 142ms. In Part 1, verdicts became traces: the graph walk that found ₦4.1 million at the third hop. And here, behavior became action: a score that watched the account stop being itself and froze it before anyone was awake to notice.
The compounding effect is the part we care about most. Every signal written against one account improves the baselines and flagged-IP sets that protect every other account. A device fingerprint burned at one institution is burned at all of them, across the eight countries we operate in. Every institution on the network makes the others safer, and the attacker's cost of doing business rises with every attempt. That is the whole thesis of Finhaq, and it is where this series ends.
Next in the series: start the series from the beginning, with the graph architecture that makes fund tracing possible. Read part 1.
Watch a trust score collapse in real time
In a 30-minute demo we will replay the 03:12 incident against a sandbox account: the signal ledger filling up, the score crossing 20, the auto-freeze firing, and the case landing with evidence attached.