Skip to content
LITE
Fraud & AI

Fraud scoring, and why it fails open

5 min readUpdated August 2026Applies to: Lite & Pro

The free plugin scores nothing. Every referral it creates gets a neutral verdict — approved, marked degraded — because there is no scorer to ask. Pro attaches one to the same seam, and nothing else about the referral changes shape when it does.

Three signals, not a model#

Pro’s scorer is three weighted rules, not a trained model:

  • Self-referral — the buyer is the affiliate. +0.6.
  • Velocity — referrals from the same affiliate in the last 24 hours. Over 20, +0.35. Over 8, +0.2.
  • Amount — the commission itself. Over 1,000 (in your store’s currency), +0.15.

The weights add up and clamp at 1.0. At 0.55 or higher the referral is held for review; at 0.85 or higher it is rejected outright, not just flagged. Below 0.55 it is approved. Each fired signal comes back with a plain-language reason — “High velocity: 23 referrals in 24 hours” — so a held commission has an answer attached rather than a number.

It was ported rule for rule from a retired Python service that ran the same three weights over HTTP. Moving it into PHP removed the network hop entirely: the scorer makes no external call, so there is nothing to time out, and nothing to fail because a third-party host was slow to answer.

It fails open#

The free plugin ships the seam this attaches to, and nothing else — no signals, no weights, no thresholds. Ask it to score a referral and it hands back the same answer every time: approved, score 0, marked degraded. That is not a fallback for a broken scorer; it is the entire free behaviour, because there is no scorer.

Once Pro’s filter is attached, a verdict is trusted only if it is an array with a numeric score and an action of approve, review or block. Anything else — a missing field, a score that isn’t a number, an unrecognised action, or the scorer throwing — collapses back to the same neutral, degraded verdict. A misbehaving scorer costs you a data point, never a commission. The scoring call itself is wrapped in a try/catch: a scorer that fatals produces the neutral verdict rather than a broken checkout, which matters because the risk assessment sits on the same code path as recording the referral in the first place.

This is deliberate and not a setting you can flip. Failing closed would mean a scorer outage silently stops crediting every affiliate you have, and you find out from a complaint instead of a log line. A referral that should have been held but wasn’t costs you a review you’d have wanted; a referral that should have been credited but was silently rejected because a scorer hiccupped costs an affiliate their commission and costs you their trust, with no log entry pointing at why.

A score is clamped, not refused#

A scorer answering with something out of range doesn’t get thrown out for that alone — a score of 1.4 is treated as “as risky as it gets” and clamped to 1.0 rather than rejected as malformed, because every threshold downstream expects a 0–1 scale and a clamp is cheaper than a rejection for an add-on that miscalculates a weight. The shape still has to be right — an array with a numeric score and a recognised action — but the number itself is forgiving.

What a verdict actually does#

A verdict decides where a new referral starts, ahead of any amount-based hold you’ve configured. Three things can hold a commission, checked in this order of authority:

  1. The scorer says block — the referral is rejected outright, whatever the amount.
  2. The scorer says review — held, whatever the amount.
  3. The commission clears your review threshold — held, even though the scorer approved it. A threshold of zero disables this check entirely rather than holding everything, because “no threshold set” has to mean “no threshold” — turning the feature off by leaving it blank would otherwise turn it fully on by accident.

Anything left over is approved automatically if you’ve turned on auto-approval; otherwise it’s held. The default is to hold everything, so an existing programme’s behaviour does not silently change the day this ships.

So a score is not only a queue-sorter — a block verdict does reject on its own, without you clicking anything, and a review verdict holds a referral regardless of how small the commission is. What it never does is happen invisibly: every rejected or held referral carries the score and the reasons that produced it, and a degraded verdict is visibly degraded rather than presented as a clean approval. Open a held referral in the free plugin and you’ll see degraded: true with a score of 0 — a plain statement that nothing actually looked at it, not a claim that it passed a check.

Explaining a verdict in words#

Pro can turn a verdict’s reasons into a sentence for you, on demand — never during checkout, only when you’re looking at a flagged referral and want the plain-English version. It goes through WordPress core’s AI Client (wp_ai_client_prompt()), so you pick the provider by installing a connector and supplying your own key. There is no bundled model SDK and no API key of ours in the path. Install no connector and the explanation is simply unavailable — the scorer’s own reasons are still right there, and the referral’s fate was already decided before anything asked a model. The explanation is a convenience layered on top of a decision that has already happened, not a step the decision waits on.