Five things that actually reduce noisy fraud alerts (and three that don't)
The single biggest reduction in fraud-alert volume I’ve ever seen came from one SQL change: suppressing repeat alerts on accounts already cleared in the last 30 days. On every team I’ve worked with, this single rule cut daily alert volume by 30 to 50 percent. No new detection logic, no machine learning, no vendor tools — just one WHERE NOT EXISTS clause.
Most fraud teams come at the noisy-alert problem the opposite way: by adding more rules. That never works. It treats symptoms, adds maintenance load, and makes the system unexplainable because you can’t tell a customer or a regulator why a transaction was flagged when seven rules contributed.
The real fix is unsexy. It isn’t in the rules. It’s in the feedback loop, the metric you’re optimizing for, and the SQL you write against your own alert history.
What works (and what doesn’t)
Five things actually reduce noisy fraud alerts, in order of impact:
- Disposition discipline. Every alert gets a confirmed-fraud, confirmed-legitimate, or unknown label. No alert is silently closed.
- Repeat-offender suppression. Don’t re-alert on accounts cleared in the recent past.
- Threshold tuning against real disposition data. Quarterly, against historical labels — not against intuition.
- Killing rules with less than 2 percent precision. They’re net negative.
- Time-to-first-alert as a tracked metric per rule per confirmed fraud case. The metric nobody measures.
Three things people try that don’t work:
- Adding more rules.
- ML-based ensemble scoring without a disposition pipeline behind it. The model has nothing to learn from.
- Lowering thresholds across the board.
The rest of this post is the SQL and the reasoning for each.
What “noise” actually means
There are two definitions, and they matter.
Operational noise is the volume of alerts your team can’t keep up with. If you generate 5,000 alerts a day and your analysts can investigate 500, that’s operational noise. Reducing it doesn’t require better rules. It requires either tighter thresholds, automated suppressions for repeat false positives, or more analysts.
Signal-to-noise is the rate of true positives among alerts. If 100 of those 5,000 daily alerts are actual fraud, your signal-to-noise is 2 percent. That’s the number worth obsessing over.
The two get conflated constantly. When somebody says “we have noisy alerts” they usually mean operational noise. The fix for that is workflow, not data.
The metric to optimize: precision at the volume you can handle
The standard metric for fraud detection is precision (true positives divided by flagged events). It’s the right concept and the wrong way to apply it.
Optimizing precision in isolation gives you the system that flags five transactions a year, all of which are fraud. Useless, because the other 1,000 frauds slipped through unflagged.
What you actually want is precision constrained by the volume your team can investigate. If your analysts can clear 500 cases a day, you want the highest-precision 500 cases per day, not the highest-precision cases overall.
This reframing changes how you tune. You don’t lower thresholds to catch more fraud. You tune the scoring function so the top-500 ranked cases per day shift toward higher precision over time.
Disposition discipline (the prerequisite to everything else)
Every alert your team investigates produces a label: confirmed fraud, confirmed legitimate, or unresolved. Those labels are the asset that lets you improve the system.
The teams that get this right do four things:
- Every alert gets a disposition. No alerts get silently closed without a label.
- Disposition data is stored at the alert level, not the case level. You want to know which RULE fired and how the rule’s alert was disposed, not just the case outcome.
- Threshold tuning happens against historical disposition data, not against intuition or vendor benchmarks.
- Confirmed-fraud and confirmed-legitimate cases get sampled into a validation set that gets used to evaluate new rules before they ship.
The teams that get this wrong let dispositions become a free-text “notes” field that nobody can analyze. The data is being captured. It’s just not in a form anyone can query.
Repeat-offender suppression (the 30-50 percent win)
A large fraction of “false positive” noise comes from the same accounts triggering the same rule over and over. The fix is auto-suppression at the account-and-rule level.
If account 12345 triggers the velocity rule, gets reviewed, is confirmed legitimate, you’ve already paid the cost of investigation. The next time it triggers velocity, you don’t need another analyst to look at it. Mark it as “previously cleared” and skip.
-- Don't fire velocity alerts for accounts cleared in the last 30 days
WHERE NOT EXISTS (
SELECT 1
FROM alerts a2
WHERE a2.account_id = accounts.account_id
AND a2.rule_name = 'velocity'
AND a2.disposition = 'legitimate'
AND a2.created_at >= current_date - INTERVAL '30 days'
)
The catch is that you need to revisit cleared accounts periodically because someone “cleared” today might be compromised tomorrow. A 30-day cooldown is a reasonable default. A 7-day cooldown is too short for repeat-customer noise. A 90-day cooldown is too long for compromised-account risk.
Threshold tuning against real data
For any single rule, the question is: where on the precision-recall curve do I want to operate?
If your rule fires on velocity > N transactions per 5 minutes, you can compute precision for every value of N using historical disposition data:
WITH alert_history AS (
SELECT
velocity_5min,
disposition
FROM alerts
WHERE rule_name = 'velocity'
AND created_at >= current_date - INTERVAL '180 days'
AND disposition IN ('fraud', 'legitimate')
),
at_threshold AS (
SELECT
threshold,
count(*) FILTER (WHERE disposition = 'fraud') AS true_positives,
count(*) FILTER (WHERE disposition = 'legitimate') AS false_positives
FROM alert_history
CROSS JOIN generate_series(3, 30) AS threshold
WHERE velocity_5min >= threshold
GROUP BY 1
)
SELECT
threshold,
true_positives,
false_positives,
true_positives::float
/ nullif(true_positives + false_positives, 0) AS precision
FROM at_threshold
ORDER BY threshold;
The output is your precision curve for that rule. Pick the threshold that gives you the precision you want at the alert volume you can handle.
Most teams pick a threshold once, leave it for two years, and wonder why precision drifts. Fraud patterns change. Re-tune quarterly at minimum.
Killing rules with less than 2 percent precision
A rule that fires 1,000 times and finds 12 actual frauds has 1.2 percent precision. Your analysts spend more time clearing the 988 false positives than they save by catching the 12. The rule is net negative. Kill it.
The pushback is usually “but it catches some fraud” — true, but the cost of investigating 988 false alerts is real headcount. That headcount can be redirected to better-precision rules and net-positive coverage.
Audit your rule precision quarterly. Any rule under 2 percent goes on the chopping block unless there’s a hard regulatory requirement to keep it.
Time-to-first-alert: the metric nobody tracks
Of all the metrics worth optimizing, this is the one teams miss most.
For a confirmed fraud case, how long did it take from the first fraudulent transaction to the first alert that fired? That’s your time-to-first-alert.
It matters because every minute of delay costs money. A card-testing ring that runs for four hours before triggering an alert has done four hours of damage. A ring that triggers in three minutes has done three minutes.
The query is straightforward once your disposition pipeline tracks alerts and fraud cases as linked rows:
WITH fraud_first_txn AS (
SELECT
case_id,
min(transaction_at) AS first_fraud_txn_at
FROM transactions
WHERE case_id IS NOT NULL
AND disposition = 'fraud'
GROUP BY case_id
),
case_first_alert AS (
SELECT
case_id,
rule_name,
min(alert_at) AS first_alert_at
FROM alerts
WHERE case_id IS NOT NULL
GROUP BY case_id, rule_name
)
SELECT
cfa.rule_name,
count(*) AS confirmed_cases,
percentile_cont(0.5) WITHIN GROUP (
ORDER BY extract(epoch FROM cfa.first_alert_at - fft.first_fraud_txn_at) / 60
) AS median_minutes_to_first_alert,
percentile_cont(0.9) WITHIN GROUP (
ORDER BY extract(epoch FROM cfa.first_alert_at - fft.first_fraud_txn_at) / 60
) AS p90_minutes_to_first_alert
FROM case_first_alert cfa
JOIN fraud_first_txn fft USING (case_id)
GROUP BY cfa.rule_name
ORDER BY median_minutes_to_first_alert;
The fix when time-to-first-alert is bad is usually one of three things:
- The signal that catches them isn’t running often enough.
- The threshold is too loose, so it only fires after extensive damage.
- The signal doesn’t exist yet, and a new rule is needed.
You only know which of those by looking at the distribution. A median of 18 minutes for the velocity rule means velocity is doing its job. A median of 4 hours means velocity is too loose or too late.
What this looks like in practice
The five wins above stack. Disposition discipline is the prerequisite — without labels, the other four are guesswork. Repeat-offender suppression gives you the 30-50 percent volume cut immediately. Threshold tuning shifts precision upward over the next quarter. Killing low-precision rules reclaims analyst capacity. Time-to-first-alert tells you which rules are still working and which have drifted.
None of this involves new detection. It’s all leverage on alerts you’re already generating.
The unsexy answer is that fraud-alert quality is mostly a data problem about your own outputs, not a problem about your rules.
Next post: dashboards for fraud teams. Working on it. Less urgent than the operational stuff above.