Signals Explained
10 min read
- View as Markdown Open this page as plain Markdown (opens in a new tab)
- Open in ChatGPT Ask questions about this page (opens in a new tab)
- Open in Claude Ask questions about this page (opens in a new tab)
- Open in Google AI Studio Ask questions about this page (Google account required) (opens in a new tab)
A signal is a single contribution to a trust score. Each detection module emits at most one signal per customer — its score is the sum of every rule that fired in that module, and its reason text lists those rules separated by semicolons. The sum of all signals, plus the base score of 50, plus the account-age bonus, equals the final trust score. This guide is the reference for every signal TrustLens emits: what it means, what triggers it, what magnitude it has, and what to do when you see it.
The Signal Object #
Every signal is recorded as a row in the {prefix}trustlens_signals table and shown in the Signal impact panel on the Customer Detail page. The fields:
| Field | Type | Description |
|---|---|---|
module |
string | Module ID that emitted the signal — returns, orders, coupons, chargebacks, linked_accounts, shipping_anomalies, card_testing, store_network (Pro), or account_age, plus system for the limited-history note. (Category-aware risk never emits its own signal — it adjusts the Returns signal.) |
score |
integer | Positive or negative contribution. Typical range -50 to +20. |
reason |
string | Human-readable explanation, often containing the specific number that triggered the signal |
Signals are re-computed and re-written on every recalculation. The signal log shows the current state, not history; store-wide movement is shown on the Dashboard’s trust-score trend chart.
Returns Module Signals #
The Returns module emits one signal per customer, built from the refund ledger. It adds up raw points from the rules below, then multiplies them by the product-risk multiplier (0.5×–2×, default 1.0× — see Category-Aware Risk below) and by a sample-size confidence factor, and caps the result at -50. The high and critical return-rate thresholds are set in Settings → Refunds → Return Risk Thresholds.
Scoring window: the last 90 days when the customer has counted orders in that window, otherwise their lifetime history. Store baseline: your store-wide return rate (refunded orders ÷ counted orders across all customers).
Return Rate Tier #
Only one tier applies.
| Trigger | Raw points |
|---|---|
| Return rate ≥ critical threshold (default 60%), or ≥ 50% and at least 3× the store baseline | 30 |
| Return rate ≥ high threshold (default 40%), or ≥ 30% and at least 2× the store baseline | 20 |
| Return rate ≥ 25% | 10 |
Reason text: “5 of 9 eligible orders were refunded in the scoring window (56%; store baseline 8.0%; confidence 83%)”. There is no positive return signal — a clean return history simply adds nothing.
Refund Value #
Refund value as a share of the customer’s completed purchase value (compared within the same currency) — relative, not absolute.
| Trigger | Raw points |
|---|---|
| Refund value ≥ 75% of purchase value | 15 |
| ≥ 50% | 10 |
| ≥ 30% | 5 |
Reason text: “Refund value is 62% of completed purchase value in the comparable currency”.
Return-Pattern Evidence #
| Trigger | Raw points |
|---|---|
| 2 delivered orders fully refunded with item-level return evidence (wardrobing) | 8 (12 at 3+) |
| 2+ delivered, evidenced partial refunds of at least 80% of the order value | 5 |
| 2+ delivered, evidenced returns refunded at 80% or more of the return window after the order (day 24+ with the default 30-day window) | 5 |
| The same refund reason on 3+ refunded orders | 5 |
| 3+ refunded orders used coupons or store credit, and the rate is at or above the high threshold | 4 |
| 2+ strongly linked accounts also have refunded orders, and the rate is at or above the high threshold | 5 |
Full refunds with no delivery evidence and shipping-only refunds are noted in the reason text but kept out of the post-delivery pattern rules.
Confidence #
The raw total is multiplied by a confidence factor that grows with the number of counted orders in the scoring window (1 − e−orders/5): about 18% at 1 order, 45% at 3, 63% at 5, 86% at 10 and 95% at 15. This is why one refund on a first order barely moves a new customer, and why the Returns module still runs below the Full-confidence Order Threshold.
Example: 25 raw points × 1.0 multiplier × 0.86 confidence = -22.
Orders Module Signals #
Tracks completion patterns. The Orders module uses clean orders (counted orders minus refunded orders) as the basis for loyalty bonuses. Rules in this module add together into one Orders signal.
| Trigger | Score | Reason Text |
|---|---|---|
| 10+ clean orders (total − refunds) | +15 | “10 orders without issues” |
| 5+ clean orders | +10 | “5 orders without issues” |
| 3+ clean orders | +5 | (no reason text) |
| Net customer value (total − refund value) ≥ $1,000 | +5 | “High customer value: $X” |
| 3+ cancellations and rate ≥ 50% | -15 | “High cancellation rate: 60%” |
| 3+ cancellations and rate ≥ 30% | -10 | “Elevated cancellation rate: 35%” |
The cancellation signals are gated by an absolute count of 3+ cancelled orders (not just a percentage) to suppress noise from low-volume customers.
Coupons Module Signals #
Catches coupon abuse patterns. The dominant signal is coupon-then-refund cycles — placing an order with a coupon, then refunding it.
| Trigger | Score | Reason Text |
|---|---|---|
| 3+ coupon-then-refund cycles | -25 | “3 coupon orders refunded (abuse pattern)” |
| 2 coupon-then-refund cycles | -15 | “2 coupon orders refunded” |
| 1 coupon-then-refund cycle | -5 | (no reason text) |
| first_order_coupons > 0 AND coupon_then_refund > 0 | -10 (additional) | “First-order coupon abuse pattern” |
| 5+ orders with coupon usage rate ≥ 80% | -10 | “High coupon usage: 85% of orders” |
| 3+ coupons used with 0 refund cycles | +5 | “Legitimate coupon user” |
The coupon-then-refund cycle is the strongest single signal of bad-faith abuse. The first-order-abuse penalty stacks on top of the cycle tier when both first_order_coupons and coupon_then_refund are non-zero — a customer with 3+ refund cycles where at least one used a first-order coupon sees -25 + -10 = -35 from this module alone.
Category-Aware Risk Signals #
Category-aware risk (shown in Settings as Product Risk Intelligence) emits no signal of its own, so returns are never penalized twice. Instead it produces the product-risk multiplier that the Returns signal is scaled by, weighted by the refund value behind each product:
- Each refunded item starts from its category multiplier, set per category in Settings → Refunds → Product Risk by Category (0.5×–2×, default 1.0× for every category; with several categories the highest applies)
- Product facts add to it: +0.15 for a SKU with a 50%+ return rate (+0.08 at 30%+) across 3+ orders, +0.10 for a SKU with a 10%+ chargeback rate, +0.08 for serial-controlled items, +0.07 for low resale recovery
- The weighted result is clamped to 0.5×–2×; refund value that can’t be mapped to a line item uses the neutral 1.0×
When the multiplier differs from 1.0, the Returns reason text includes a line such as “Product risk multiplier: 1.25× — high SKU return rate”.
Linked Accounts Signals #
Detects accounts that share an identity-grade identifier — shipping address, billing address, phone number, or payment method token. A shared IP address or browser (user-agent) is recorded but never links accounts on its own. The penalty is added through the trustlens/score_signals filter, so it applies even below the Full-confidence Order Threshold.
| Trigger | Score |
|---|---|
| Linked to 1–2 other accounts | -5 |
| Linked to 3–4 other accounts | -10 |
| Linked to 5+ other accounts | -15 |
| Each linked account in the Risk or Critical segment | an additional -5 (the Link penalty in Settings → Detection, 0–20) |
| Each linked account that is blocked | an additional 2× the Link penalty (-10 by default) |
These add up into one signal with the reason text “Linked to 2 other account(s) (1 high-risk, 0 blocked)”.
The detection is one-way at signal time but bidirectional in the data — both accounts in a link see their counterpart in the linked_accounts list on the customer detail page.
Shipping Anomalies Signals #
| Trigger | Score | Reason Text |
|---|---|---|
| Distinct shipping addresses ÷ orders above 0.8 / 0.5 / 0.3 | -15 / -10 / -5 | “High address diversity: 4 unique shipping addresses” |
| Billing/shipping country mismatch | -3 | “Billing/shipping country mismatch detected” |
| Country mismatch on 2+ shipping addresses | -10 | “Billing/shipping country mismatch across 2 orders” |
| 5+ / 3–4 new shipping addresses within the velocity window (default 30 days) | -10 / -5 | “3 new shipping addresses in 30 days” |
Rules add into one signal, capped at -50, and the module waits until the customer reaches the Full-confidence Order Threshold. Pro adds diversity-trend detection (-5 / -10 when recent new addresses run at 2× / 3× the customer’s own average) and a multi-country reshipping rule (-10 for two shipping countries with one billing country, -15 for three or more). There is no “high-risk corridor” list.
Chargebacks Signals #
The strongest signals in the system. A single lost chargeback can move a customer from Trusted to Risk. Like the other history modules, Chargebacks waits until the customer reaches the Full-confidence Order Threshold.
| Trigger | Score | Reason Text |
|---|---|---|
| 3+ lost disputes | -50 | “3 chargebacks lost (major risk)” |
| 2 lost disputes | -40 | “2 chargebacks lost” |
| 1 lost dispute | -30 | “Has lost chargeback” |
| Any dispute still pending | -20 | “1 pending dispute(s)” |
| Won disputes and no lost ones | -5 | “Previously disputed (won)” |
| 5+ orders and disputes on 10%+ of them | -15 (additional) | “High dispute rate: 20%” |
| 10+ orders and no disputes | +10 | “No dispute history” |
Won disputes (“dispute reversed in our favor”) count significantly less than lost ones — there’s still a small penalty because the dispute was filed, but the magnitude is much lower than a lost dispute.
Card-Testing Signals #
Fires when the customer submitted checkout from a device fingerprint that Card-Testing Defense has caught and flagged.
| Trigger | Score | Reason Text |
|---|---|---|
| Customer linked to 1+ device fingerprints caught card testing | -30 (flat) | “Linked to 1 device fingerprint(s) involved in a card-testing attack” |
This is the bridge between checkout-time velocity detection and the customer trust score: if a fingerprint that hammered your gateway with stolen-card attempts is tied to a customer’s checkout, that customer inherits the signal. The link comes from the checkout velocity events, so it lasts only as long as those events are retained (48 hours by default), and like the other history modules it waits until the customer reaches the Full-confidence Order Threshold.
Account Age Bonus #
Not strictly a module signal — added after module signals are collected. Mutually exclusive tiers.
| Tenure | Score | Reason Text |
|---|---|---|
| 1+ year (365+ days since first order) | +15 | “Long-term customer (1+ year)” |
| 6+ months (180+ days) | +10 | “Established customer (6+ months)” |
| 3+ months (90+ days) | +5 | “Regular customer (3+ months)” |
| < 3 months | 0 | (no signal recorded) |
See Account Age Loyalty Bonus for the full design.
System Signals #
The scoring engine records a zero-score system signal that exists only to convey state. Module results with a zero score and no reason text are dropped.
| Trigger | Score | Reason Text |
|---|---|---|
| Customer below the Full-confidence Order Threshold (default 3) | 0 | “Limited local history (1/3 orders); return risk is confidence-weighted” — most modules are skipped, but Returns (confidence-weighted), linked accounts and the age bonus still count |
| Module disabled in settings | — | (module returns nothing; no signal recorded) |
| Allowlisted | — | (short-circuit; no signals computed) |
Reading the Signal Breakdown #
The Signal impact panel on the Customer Detail page is a complete audit of how the score was calculated. To verify a score manually:
- Open the customer’s profile
- Note every signal in the breakdown with its score and reason
- Sum all signal scores (positive and negative)
- Add 50 (base score)
- Clamp to 0–100 if needed
- The result should match the displayed score exactly
If the numbers don’t reconcile, the customer is either currently being recalculated (a stale UI displaying the old score with new signals) or there’s a custom filter modifying the score via trustlens/trust_score.
Signal Strength Comparisons #
To build intuition for signal magnitudes:
| Comparison | Magnitudes |
|---|---|
| “Worst” single negative | 3+ lost disputes: -50 (more with a pending dispute or a high dispute rate on top) |
| “Worst” common negative | Returns signal: capped at -50; a critical-tier return rate alone is up to -30 |
| “Best” single positive | 10+ clean orders: +15; 1+ year tenure: +15 |
| Typical first negative signal | Return rate at 25%+: up to -10 |
| Typical first positive signal | 3+ clean orders: +5 |
| Maximum theoretical sum of positives | +50 (Orders +20, Chargebacks +10, Coupons +5, age bonus +15) |
| Maximum theoretical sum of negatives | Unbounded below 0 (clamped) — compound signals can stack arbitrarily |
Asymmetry: positive signals are bounded, negative signals are unbounded (clamped at 0). This is intentional — a customer with multiple severe abuse patterns should read as Critical, not just “below average.”
Filtering Signals Programmatically #
Developers can modify the signals list before the final sum using the trustlens/score_signals filter:
add_filter( 'trustlens/score_signals', function( $signals, $email_hash ) {
// Add a custom signal from external data
$external_score = my_loyalty_program_score( $email_hash );
if ( $external_score > 0 ) {
$signals[] = array(
'module' => 'loyalty_program',
'score' => min( 10, $external_score ),
'reason' => 'Loyalty program member',
);
}
return $signals;
}, 10, 2 );
The custom signal will appear in the signal breakdown alongside built-in ones, with whatever module ID you provide.
Updated on October 18, 2026