Skip to content

Fraud & Payment Security

Fraud and payment security is the discipline of protecting money in motion: the transaction systems (card processing, wire transfers, payment platforms), the fraud typologies that attack them (BEC wire fraud, account takeover, card-not-present fraud, authorized push payment scams, internal embezzlement), and the control architecture that separates legitimate transactions from fraudulent ones without breaking the business. The discipline differs from intrusion defense in its objective function: the adversary isn’t after data access — they’re after value extraction, the attack surface is business process as much as technology, and the detection signal is often behavioral anomaly (this payment doesn’t match this account’s history) rather than technical indicator. The craft’s center: payment fraud controls are a three-layer architecture — prevention (verification ceremonies: out-of-band callbacks, dual approval, beneficiary lockout), detection (velocity, deviation, counterpart-newness analytics), and response (recall/reversal windows measured in minutes — the fraud clock is the fastest clock in security, and BT19’s crisis discipline applies at wire speed).

Payment fraud is the intrusion class with a direct P&L line: no data to sell, no ransom to negotiate — just money leaving. The typologies map directly to this handbook’s modules: BEC wire fraud rides email compromise (BT14’s thread hijacking) and identity gaps (BT15’s session theft); account takeover rides credential stuffing; card fraud rides skimming and card-testing automation; and the authorized-push-payment scams (victims themselves approving transfers to fraudsters) ride social engineering (S4’s class). The defender’s challenge is structural: every friction added to fraud adds friction to business — the verification ceremony that stops the €180k wire also slows the legitimate one — so the craft is risk-tiered friction: strong ceremony for new beneficiaries and out-of-pattern amounts, fluid paths for established patterns. The fraud clock makes it urgent: international wire recalls are measured in hours, card chargebacks in days — detection latency that intrusion response would tolerate is fatal here.

The failure modes are control-shaped. The approval theater: dual approval where the second approver rubber-stamps (the same Slack thread, the same urgency) — the control exists, the independence doesn’t. Callback to the number in the email: the verification ceremony that calls the number the fraudster provided — the callback must hit the registered number (BT14’s out-of-band doctrine, verbatim). Velocity-blind systems: limits per-transaction but not per-window — the fraudster who bills 500 victims €900 each defeats the €10,000 single-transaction limit. The disconnected fraud/SOC split: fraud alerts in the payments team’s queue, intrusion signals in the SOC’s — the account-takeover-then-wire pattern (the intrusion is the fraud) falls in the gap between them. And recovery-first thinking: assuming the recall process will fix it — recall success decays by the hour; prevention and detection are the only reliable layers.

THE PAYMENT FRAUD STACK
+------------------------------------------------------+
| PREVENTION (ceremonies) |
| beneficiary changes: out-of-band callback to |
| REGISTERED number + dual independent approval |
| new beneficiaries: cooling period + small-test? no |
| (test transactions teach fraudsters) — verify, |
| not probe |
| high-value: dual approval, separate channels |
+-------------------------+----------------------------+
v
+------------------------------------------------------+
| DETECTION (behavioral analytics) |
| velocity (per account/window), deviation from |
| history (amount, counterpart, hour), counterpart |
| newness, session context (device, geo — BT15 join) |
+-------------------------+----------------------------+
v
+------------------------------------------------------+
| RESPONSE (the fraud clock) |
| hold -> verify -> recall request -> ledger freeze |
| minutes matter; BT19 comms; evidence for BT5 |
+------------------------------------------------------+

Prevention ceremonies are process controls with teeth: beneficiary-detail changes (the BEC classic) require a callback to the registered number — the one from the master file, never the one in the request — plus dual approval where the approvers are independent (different chains, no shared urgency thread). New beneficiaries get a cooling period with no probe payments (a small “test” transaction teaches the fraudster exactly which details verify correctly — the counterintuitive doctrine that separates mature treasury from naive). High-value thresholds route to separate authorization channels (the CFO approves in the treasury system, not by replying to the email that requested it). The ceremonies fail loudly when tested (S4’s exercise class) — which is why the fraud program and the social-engineering program share a calendar.

Detection analytics run on payment streams the way BT9’s rules run on telemetry: velocity (transactions per account per window — the card-testing pattern is hundreds of small authorizations), deviation (this account’s amounts, counterparts, hours — a €48,000 wire from an account that’s never exceeded €3,000 is the alert), counterpart-newness (first-ever beneficiary + maximum amount = the BEC signature), and session context (the wire initiated from a session that logged in from a new country 20 minutes ago — BT15’s risk signals joined into the payment decision). The tuning discipline is BT11’s: thresholds tuned to FP rates measured against real business, with the finance team as the FP partner (every false hold has a business cost they can quantify).

Response runs on the fraud clock: the hold (can the payment system hold a pending transaction on anomaly? that capability is a control), verification (the callback that confirms or clears), the recall request (to the receiving bank — success decays hourly), the ledger freeze (the mule account’s entry point), and the evidence pack (BT5’s chain — the fraud investigation feeds law-enforcement referral and insurance). BT19’s crisis discipline applies compressed: the notification decision (customer, regulator, insurer) runs on pre-agreed thresholds because there’s no time to convene.

Deep dive: why the callback must hit the REGISTERED number — the anatomy of verification theater

The callback ceremony’s entire value lives in one property: the verification channel is independent of the request. Call the number in the email and you are verifying with the attacker; call the number in the master vendor file and you are verifying with the world as it existed before the attack began. The failure is never ignorance (“nobody told us to call back”) — it’s mechanism: the request’s contact details sit one click away, the master file takes a search, and under urgency the near number wins. The fix is therefore mechanical, not motivational: the approval workflow displays only registered contact details (the request’s own contacts masked or stripped), the callback field records which source was used (auditable — benchgcheck reads it), and the procedure’s exception path (vendor genuinely changed numbers) routes through a second out-of-band check (the account’s registered email, the relationship owner’s confirmation) rather than the new number alone. Verification theater is the most dangerous control state in fraud defense: it produces audit evidence of a control that never protected anything. Make the right path the only path, and the ceremony stops depending on a human winning an argument with urgency.

TechniqueWhat it doesWhere it failsNotes
Out-of-band callbackVerify via REGISTERED numberCallback to provided numberThe ceremony’s core
Dual independent approvalTwo chains, no shared urgencyRubber-stamp second approverIndependence = control
Beneficiary cooling periodNew counterparties delayedProbe payments teach fraudstersVerify, never probe
Velocity analyticsRate per account/windowPer-transaction limits onlyCard-testing killer
Deviation analyticsAmount/counterpart/hour historyCold-start accountsBT7’s baseline, payments
Counterpart-newnessFirst-ever + max amountBeneficiary sprawlThe BEC signature
Session-context joinDevice/geo risk into paymentFraud and SOC splitBT15 telemetry join
Payment hold capabilityAnomaly pauses transactionHold needs process ownerThe fraud clock’s tool
Recall workflowBank recall requests, hourly decayRecovery-first thinkingPrevention is the layer
Fraud/SOC correlationATO-then-wire as one caseSplit queues, split truthThe gap that costs

fraudgate — a payment screening engine: reads a transaction export (CSV: ts, account, amount, currency, beneficiary, beneficiary_new, channel) with account history (CSV: account, avg_amount, max_amount, counterpart_count, active_since), scores each transaction on velocity, deviation, counterpart-newness, and timing, and routes: PASS / REVIEW (hold queue with reasons) / BLOCK. Deterministic given inputs — the screening rule’s unit test.

#!/usr/bin/env python3
"""fraudgate — payment screening: velocity, deviation, newness, timing.
Inputs:
txns.csv: ts,account,amount,currency,beneficiary,beneficiary_new,channel
(ts ISO; beneficiary_new: yes/no — first-ever payment to it)
history.csv: account,avg_amount,max_amount,counterpart_count,active_since
Routes: score >= 70 BLOCK, >= 40 REVIEW (hold), else PASS.
Reasons attach to every route — screening without reasons is unappealable.
"""
import argparse
import csv
import sys
from collections import defaultdict
from datetime import datetime
from pathlib import Path
REQ_T = ["ts", "account", "amount", "currency", "beneficiary",
"beneficiary_new", "channel"]
REQ_H = ["account", "avg_amount", "max_amount", "counterpart_count",
"active_since"]
BLOCK = 70
REVIEW = 40
NEW_BENEFICIARY_PENALTY = 25
DEVIATION_WEIGHT = 30 # amount vs account max
HOUR_PENALTY = 10 # outside business hours
CHANNEL_PENALTY = {"mobile": 5, "web": 0, "api": 15, "branch": 0}
def fail(msg):
sys.exit(f"fail-loud: {msg}")
def load(path, req, label):
if not path.exists():
fail(f"{label} not found: {path}")
with path.open(encoding="utf-8", errors="replace") as fh:
reader = csv.DictReader(fh)
missing = [c for c in req if c not in (reader.fieldnames or [])]
if missing:
fail(f"{label} missing columns: {missing}")
rows = list(reader)
if not rows:
fail(f"{label} empty — screening against no history is guessing")
return rows
def main():
ap = argparse.ArgumentParser(description="payment screening")
ap.add_argument("txns", type=Path)
ap.add_argument("--history", type=Path, required=True)
ap.add_argument("--out", type=Path, default=Path("fraudgate_out"))
args = ap.parse_args()
txns = load(args.txns, REQ_T, "transactions")
history = {h["account"].strip(): h
for h in load(args.history, REQ_H, "history")}
# velocity: transactions per account per hour (this batch's view)
per_hour = defaultdict(lambda: defaultdict(int))
parsed = []
for n, t in enumerate(txns, 2):
try:
ts = datetime.fromisoformat(t["ts"].replace("Z", "+00:00"))
except ValueError:
fail(f"txns line {n}: unparseable ts {t['ts']!r}")
try:
amount = float(t["amount"])
except ValueError:
fail(f"txns line {n}: non-numeric amount {t['amount']!r}")
acct = t["account"].strip()
hour_key = (acct, ts.strftime("%Y-%m-%dT%H"))
per_hour[hour_key]["n"] = per_hour[hour_key].get("n", 0) + 1
parsed.append({"n": n, "ts": ts, "account": acct,
"amount": amount,
"beneficiary": t["beneficiary"].strip(),
"ben_new": t["beneficiary_new"].strip().lower()
in ("yes", "true", "1"),
"channel": t["channel"].strip().lower()})
results = []
for t in parsed:
score, reasons = 0, []
acct = t["account"]
h = history.get(acct)
# velocity within this batch
vel = per_hour[(acct, t["ts"].strftime("%Y-%m-%dT%H"))]["n"]
if vel >= 10:
score += 30
reasons.append(f"velocity: {vel} txns in hour (>=10)")
elif vel >= 5:
score += 15
reasons.append(f"velocity: {vel} txns in hour")
# counterpart newness
if t["ben_new"]:
score += NEW_BENEFICIARY_PENALTY
reasons.append("first-ever beneficiary")
# deviation from account history
if h is None:
score += 20
reasons.append("no account history (cold account)")
else:
try:
max_amt = float(h["max_amount"])
avg = float(h["avg_amount"])
except ValueError:
fail(f"history for {acct}: non-numeric amounts")
if max_amt > 0 and t["amount"] > max_amt:
ratio = t["amount"] / max_amt
pts = min(DEVIATION_WEIGHT, int(10 * ratio))
score += pts
reasons.append(f"amount {t['amount']:.0f} exceeds account "
f"max {max_amt:.0f} (x{ratio:.1f})")
elif avg > 0 and t["amount"] > 3 * avg:
score += 10
reasons.append(f"amount > 3x account average")
# timing
hour = t["ts"].hour
if hour < 6 or hour >= 22:
score += HOUR_PENALTY
reasons.append(f"outside business hours ({hour:02d}:00)")
# channel risk
score += CHANNEL_PENALTY.get(t["channel"], 0)
if t["channel"] not in CHANNEL_PENALTY:
reasons.append(f"unknown channel {t['channel']!r} (+0 — "
"extend the table)")
route = ("BLOCK" if score >= BLOCK
else "REVIEW" if score >= REVIEW else "PASS")
results.append({"route": route, "score": score,
"ts": t["ts"].isoformat(), "account": acct,
"amount": f"{t['amount']:.2f}",
"beneficiary": t["beneficiary"],
"reasons": "; ".join(reasons) or "clean pattern"})
results.sort(key=lambda r: -r["score"])
args.out.mkdir(parents=True, exist_ok=True)
with (args.out / "screening.csv").open("w", newline="",
encoding="utf-8") as fh:
w = csv.DictWriter(fh, fieldnames=["route", "score", "ts",
"account", "amount",
"beneficiary", "reasons"])
w.writeheader()
w.writerows(results)
routes = defaultdict(int)
for r in results:
routes[r["route"]] += 1
print(f"screened={len(results)} PASS={routes['PASS']} "
f"REVIEW={routes['REVIEW']} BLOCK={routes['BLOCK']}")
for r in results:
if r["route"] != "PASS":
print(f" [{r['route']:<6}] {r['score']:>3} {r['account']} "
f"->{r['beneficiary'][:20]} {r['amount']}")
print(f" {r['reasons'][:80]}")
print(f"register: {args.out / 'screening.csv'}")
print("remember: REVIEW = hold + human verify via REGISTERED callback; "
"BLOCK = hold + fraud analyst; reasons make it appealable")
if __name__ == "__main__":
main()

The velocity model (lines 62-70, 87-93) counts transactions per account per calendar hour within the batch — the card-testing signature is volume-per-time, not volume-per-transaction, and the two-tier thresholds (≥10/hour = 30 points, ≥5 = 15) catch both the automated burst and the manual grind. The counterpart-newness penalty (lines 96-99, flat 25) is the BEC signature’s core: a first-ever beneficiary alone doesn’t block, but combined with deviation it clears REVIEW — the composite is the design (single signals score, stacked signals route).

The deviation model (lines 101-116) is account-history-relative: exceeding the account’s max scores by ratio (capped at 30), exceeding 3× average scores 10 — two tiers because max-exceedance (“this account has never moved this much”) and average-anomaly (“this is 4× their normal”) are different risk shapes. The cold-account penalty (20, lines 103-105) handles the fraudster’s fresh accounts — no history is itself a signal. The timing and channel tables (lines 118-125) are the tunable business context: the CHANNEL_PENALTY dict’s unknown-channel branch (lines 127-129) adds zero points but says so in reasons — an unknown channel appearing repeatedly is a data-quality finding the tuning review catches, not a silent gap.

Every route carries reasons (line 131 — “clean pattern” for passes too), because the screening’s legitimacy with the finance team depends on appealability: the analyst who releases a REVIEW hold can read why it scored, and the FP-tuning review (BT11’s discipline, payments edition) works from reasons, not vibes.

The screening runs inline on the payment rails: each transaction scores before release — PASS flows, REVIEW holds in the queue for human verification (the callback to the registered number), BLOCK holds for the fraud analyst. The BEC wire arrives: €48,000, first-ever beneficiary, initiated 23:40 from a session that logged in an hour ago from a new country. Score: newness 25 + max-exceedance 30 + hours 10 = 65 — REVIEW, held. The callback goes to the registered finance number (not the one in the email thread — the thread is the attack); the finance director confirms she never authorized it; the hold becomes a kill, the compromised mailbox goes to BT14’s incident path, the session context goes to BT15 (revocation — the session is still alive), and the attempted-beneficiary account goes to the abuse desk for reporting.

The velocity pattern catches the other class: 400 card authorizations of €1 from one account in an hour (the card-testing run — stolen card numbers being validated before resale). Score: velocity 30 + cold-account context — BLOCK mid-run, the remaining authorizations never execute, and the issuer’s fraud team gets the blocked BIN list. The correlation layer joins fraud and SOC (the control that fails most often): the account-takeover-then-wire pattern means the SOC’s impossible-travel alert and the fraud gate’s hold are the same case — the shared queue (or at minimum the shared case ID) is the fix, and BT19’s notification thresholds (customer, regulator, insurer) are pre-agreed so the response runs at fraud-clock speed, not meeting speed.

  1. Screen — every transaction scored: velocity, deviation, newness, timing, channel (reasons attached)
  2. Hold — REVIEW/BLOCK route to hold; the fraud clock starts (recall decays hourly)
  3. Verify — callback to REGISTERED number; independence-checked approval; no probe payments
  4. Correlate — join SOC signals (session risk, impossible travel) — ATO-then-wire is one case
  5. Respond — kill or release; recall request for escaped funds; ledger freeze on mule entry
  6. Report — BT19 notifications on pre-agreed thresholds; BT5 evidence for law enforcement
  7. Fix — ceremony redesign (callback source, approval independence), threshold tuning from FPs
  8. Re-test — S4’s payment-pretext exercise measures the ceremony live; BT20 tracks the rates

The BEC wire starts with a beneficiary change; benchgcheck audits that process: reads the change requests log (CSV: ts, account, old_beneficiary, new_beneficiary, callback_done, callback_number_source, approved_by, second_approver) and scores each change against the ceremony doctrine — callback to REGISTERED source (not the request’s own contact), independent second approver (not the same thread/approver), and the timing rule (changes and first payments within 24h = the fraudster’s rush signature).

Callback-source truth: the finding class that matters is callback-to-provided (the ceremony ran but called the number the request supplied — verification theater); the register must carry which source the callback used. Independence check: second approver equals first approver, or both appear in the same email thread (approximated by same-request timestamps within minutes), fails independence. Rush detection: beneficiary change followed by first payment to that beneficiary within 24 hours is the fraudster’s timeline — its own flag regardless of ceremony correctness (ceremony + rush together = the classic). Fail-loud on register drift.

#!/usr/bin/env python3
"""benchgcheck — beneficiary-change ceremony audit.
Input changes.csv: ts,account,old_beneficiary,new_beneficiary,
callback_done,callback_number_source,approved_by,
second_approver,first_payment_hours
(callback_number_source: registered|provided|none;
first_payment_hours: empty if none yet)
Findings: CALLBACK-TO-PROVIDED, NO-INDEPENDENCE, RUSH-FIRST-PAYMENT,
NO-CALLBACK."""
import argparse
import csv
import sys
from pathlib import Path
REQ = ["ts", "account", "old_beneficiary", "new_beneficiary",
"callback_done", "callback_number_source", "approved_by",
"second_approver", "first_payment_hours"]
def main():
ap = argparse.ArgumentParser(description="beneficiary change audit")
ap.add_argument("changes", type=Path)
ap.add_argument("--out", type=Path, default=Path("benchg_out"))
args = ap.parse_args()
if not args.changes.exists():
sys.exit(f"fail-loud: register not found: {args.changes}")
with args.changes.open(encoding="utf-8", errors="replace") as fh:
rows = list(csv.DictReader(fh))
for r in rows:
missing = [c for c in REQ if c not in r]
if missing:
sys.exit(f"fail-loud: register missing columns: {missing}")
if not rows:
sys.exit("fail-loud: empty register — no changes is not a clean "
"process, it's a broken export")
findings = []
for n, r in enumerate(rows, 2):
cid = f"line {n} ({r['account']} -> {r['new_beneficiary'][:20]})"
cb = r["callback_number_source"].strip().lower()
if r["callback_done"].strip().lower() != "yes":
findings.append({"kind": "NO-CALLBACK", "ref": cid})
elif cb == "provided":
findings.append({"kind": "CALLBACK-TO-PROVIDED", "ref": cid,
"detail": "callback used the request's own "
"contact — verification theater"})
elif cb not in ("registered",):
findings.append({"kind": "CALLBACK-UNKNOWN-SOURCE", "ref": cid,
"detail": f"source recorded as {cb!r}"})
a1 = r["approved_by"].strip().lower()
a2 = r["second_approver"].strip().lower()
if not a2:
findings.append({"kind": "NO-INDEPENDENCE", "ref": cid,
"detail": "no second approver"})
elif a2 == a1:
findings.append({"kind": "NO-INDEPENDENCE", "ref": cid,
"detail": "second approver = first approver"})
fp = r["first_payment_hours"].strip()
if fp:
try:
hours = float(fp)
except ValueError:
sys.exit(f"fail-loud: line {n}: first_payment_hours "
f"not numeric")
if 0 <= hours <= 24:
findings.append({"kind": "RUSH-FIRST-PAYMENT", "ref": cid,
"detail": f"first payment {hours:.0f}h after "
"change — the fraudster's rush"})
args.out.mkdir(parents=True, exist_ok=True)
with (args.out / "findings.csv").open("w", newline="",
encoding="utf-8") as fh:
w = csv.DictWriter(fh, fieldnames=["kind", "ref", "detail"])
w.writeheader()
for f in findings:
w.writerow({k: f.get(k, "") for k in ("kind", "ref", "detail")})
kinds = {}
for f in findings:
kinds[f["kind"]] = kinds.get(f["kind"], 0) + 1
print(f"changes audited={len(rows)} findings={len(findings)} {kinds}")
for f in findings[:12]:
print(f" [{f['kind']:<22}] {f['ref']}")
if f.get("detail"):
print(f" {f['detail']}")
print(f"register: {args.out / 'findings.csv'}")
print("the ceremony that calls the number in the email is the attack's "
"own control — CALLBACK-TO-PROVIDED is the finding to hunt")
if findings:
sys.exit(1)
if __name__ == "__main__":
main()

The callback-source classification (lines 31-43) is the audit’s core finding: CALLBACK-TO-PROVIDED — the ceremony executed but called the number the fraudster’s email supplied, which is verification theater worse than no verification (it documents a control that didn’t protect). The independence check (lines 44-53) catches the rubber-stamp pattern: no second approver at all, or the same approver twice — the control exists, the independence doesn’t. The rush detection (lines 54-63) applies the fraudster’s-timeline doctrine: a beneficiary change followed by payment within 24 hours is the classic BEC rush regardless of ceremony correctness — ceremony-plus-rush together is the composite signature.

The register schema’s first_payment_hours field (empty when no payment yet) keeps the audit joinable with the screening log — a change flagged RUSH whose payment fraudgate held is the composite case, routed with both findings attached. The closing line (before exit) names the module’s sharpest truth: the callback that calls the attack’s own number is the attack’s own control.

screen a day’s payment export against account history

Section titled “screen a day’s payment export against account history”

$ python3 fraudgate.py txns.csv —history history.csv screened=1841 PASS=1798 REVIEW=38 BLOCK=5 [REVIEW] 65 ACCT-4471 ->newvendor-llc 48000.00 first-ever beneficiary; amount 48000 exceeds account max 3000 (x16.0); outside business hours (23:00) [BLOCK ] 92 ACCT-9912 ->card-test-batch 1.00 velocity: 412 txns in hour (>=10) register: fraudgate_out/screening.csv

audit the beneficiary-change process for the ceremony doctrine

Section titled “audit the beneficiary-change process for the ceremony doctrine”

$ python3 benchgcheck.py changes_register.csv changes audited=57 findings=9 {‘CALLBACK-TO-PROVIDED’: 3, ‘NO-INDEPENDENCE’: 4, ‘RUSH-FIRST-PAYMENT’: 2} [CALLBACK-TO-PROVIDED ] line 18 (ACCT-4471 -> newvendor-llc) callback used the request’s own contact — verification theater register: benchg_out/findings.csv

the composite: line 18’s change + fraudgate’s held wire = the BEC attempt

Section titled “the composite: line 18’s change + fraudgate’s held wire = the BEC attempt”

The fraud gates and the SOC share the account-takeover surface: screening reasons reference session context when available (the BT15 join — device/geo risk feeding the score), and the correlation rule (fraud hold + intrusion signal within N hours = one case) is the control that closes the split-queue gap. The FP-tuning review is BT11’s discipline with a finance partner: every false hold costs the business measurably, so thresholds tune against both missed-fraud and business-friction — the metric pair that keeps the screening honest (BT20’s counterpart doctrine). Ceremony audits (benchgcheck quarterly) and S4’s payment-pretext exercises are the process layer’s validation: configuration truth and behavioral truth, the same pairing everywhere else in this handbook.

  1. Add real-time beneficiary-network analysis (mule-account graphs — shared accounts receiving from many victims).
  2. Join session-risk telemetry directly into the score (BT15’s signals as a screening feature).
  3. Add recall-workflow tracking (recall requested, bank response, recovered amount) as the fraud-clock metric.
  4. Support multiple currencies with per-currency thresholds (the FX blind spot).
  5. Emit the composite alert (change + screening + session risk) as one case to the shared fraud/SOC queue.
SignalSourceIDNotes
REVIEW/BLOCK ratesfraudgateper dayScreening health
CALLBACK-TO-PROVIDEDbenchgcheckquarterlyVerification theater
NO-INDEPENDENCE ratebenchgcheckquarterlyApproval theater
RUSH-FIRST-PAYMENTbenchgcheckper changeThe fraudster’s rush
Velocity anomaliesfraudgateper hourCard-testing class
Counterpart-newness hitsfraudgateper txnBEC signature
Session-risk joinsBT15 telemetryper paymentATO-then-wire
Recall success by hourFraud opsper caseThe fraud clock
FP rate (business cost)Finance partnerper cycleThreshold honesty
Composite case rateFraud/SOC queueper monthThe joined view

Fraudsters evade payment controls with process-honed patience. The warmed account: weeks of small, boring, legitimate-looking transactions build the history that defeats deviation analytics — the account that “behaves” for a month before the €48,000 wire; the counter is counterpart-newness (the beneficiary is always new the first time) and velocity floors independent of history. Ceremony harvesting: the fraudster calls twice — first a harmless “confirming our bank details?” to learn the ceremony’s shape, then the real request shaped to pass it; the ceremony must be constant (the same registered-number callback every time) and never discussable (“we usually just need the number in your email” is the answer that arms the attack). Approval-window exploitation: requests timed to the approver’s known travel/absence (the CFO is at the conference; the deputy approves alone) — independence checks and delegation rules (who may approve, when, with what escalation) are the control; S4’s pretext tests exercise exactly this. The mule layer: funds hop through recruited or stolen accounts (money mules) within minutes — recall chases the hop, not the person; the ledger-freeze and network analytics (the mule graph) are the response-side controls. Test-transaction probing: small real payments to map which beneficiary details survive verification — hence the no-probe doctrine (cooling period with no probe; verify properly instead). Channel arbitrage: the fraud moves to the channel with the weakest ceremony (the API that skips callbacks because “integrations don’t call”) — CHANNEL_PENALTY’s unknown-channel branch exists because channel sprawl is the evasion; every channel gets the ceremony or gets the block.

⚠️ What caught me: our treasury’s callback ceremony was documented, trained, and audited — and the fraudster still walked €180k out through it, because the callback script said “call the vendor to confirm” and the vendor’s number was in the email. Three people executed the ceremony faithfully; every one of them dialed the attack’s own phone number. The fix wasn’t training (they knew the procedure) — it was making the procedure mechanical: the treasury system now displays only the registered number, the email’s contact details are masked from the approval workflow, and the callback field records which source was used (benchgcheck reads it). The lesson generalized into my whole practice: a control that depends on a human choosing correctly under pressure is a suggestion; a control where the system makes the right path the only path is a control. Fraud security is where that lesson costs the least to learn.

Run the fraud-control loop in the lab. Screen: build a transaction export with three classes (clean patterns, a BEC-shaped wire, a card-testing burst) and run fraudgate — confirm each routes correctly with reasons. Audit: create a beneficiary-change register with all four finding classes; run benchgcheck. Ceremony: with a teammate, run the payment-change callback — deliberately supply a “provided” number first (observe the catch), then the registered one. Correlate: fabricate a session-risk event for the BEC account and practice the composite-case routing (one case, fraud + SOC). Clock: time the hold-decision for the BEC wire — that time is your fraud-clock baseline. Debrief: which control caught what, which one the composite needed — and does your real organization’s fraud and SOC teams share a queue?

  • Technical: Walk fraudgate’s scoring model — why is counterpart-newness a flat penalty while deviation is ratio-based, and what makes stacked signals the design?
  • Technical: Why is CALLBACK-TO-PROVIDED worse than no callback, and what register field makes it detectable?
  • Scenario: A €48,000 first-ever-beneficiary wire is held; the requesting executive is furious and the callback confirms legitimacy — the deal was real. Walk the release decision and the FP lesson.
  • Scenario: The SOC’s impossible-travel alert fires 20 minutes after fraud holds a wire from the same account. Walk the composite-case response and the clock it runs on.
  • Conceptual: Why does the no-probe doctrine (cooling period without test payments) matter, and what does a small test transaction actually teach the fraudster?
  • Deep-dive: Design fraud controls for a mid-size treasury: ceremonies by payment type, the screening stack, the fraud/SOC correlation, recall workflow, and the BT20 metrics.
  • Practical: Your organization’s payment systems span three channels with inconsistent ceremonies. What do you standardize first, and what do you accept as residual risk?

BT14 (Email Security Operations) · BT15 (Identity & MFA Defense) · BT18 (Digital Risk & Brand Protection) · BT19 (Crisis Communication & Leadership) · S4 (Social Engineering Operations)