---
title: How Trust Scoring Works
description: "The TrustLens trust score is the single number that summarizes everything the plugin has learned about a customer. It's calculated from the actual order, refund, coupon, dispute, and checkout data your store generates — no opaque machine learning model, no…"
url: "https://webstepper.io/docs/trustlens/trust-scoring/how-trust-scoring-works/"
updated: "2026-10-18"
category: Trust Scoring
---

# How Trust Scoring Works

The TrustLens trust score is the single number that summarizes everything the plugin has learned about a customer. It's calculated from the actual order, refund, coupon, dispute, and checkout data your store generates — no opaque machine learning model, no external data, no inference you can't inspect. This guide explains how the calculation works end-to-end so you can predict scores, debug surprises, and justify decisions to customer service and finance teams.

---

## The Core Formula

Every score is the result of a simple deterministic calculation:

```
final_score = base_score + Σ(signal_scores) + account_age_bonus
clamped to the range 0–100
```

| Term | Value | Source |
| --- | --- | --- |
| `base_score` | 50 | Hardcoded — every customer starts neutral |
| `signal_scores` | Variable per module | Emitted by each active detection module, plus signals added through the `trustlens/score_signals` filter (this is how the Linked Accounts penalty is applied) — can be positive or negative |
| `account_age_bonus` | 0, +5, +10, or +15 | Tenure-based loyalty bonus |
| Clamp | 0 ≤ score ≤ 100 | Applied last, after all signals sum |

There is no weighted average, no normalization, no z-score. The score is a sum, clamped. That's deliberate — it means you can look at the signals list on the customer profile and add them up yourself to verify the result.

---

## Calculation Walkthrough

Here's how a real customer's score is computed, step by step.

### Step 1: Check Allowlist

If the customer is on the allowlist, the calculation short-circuits and returns **score = 100, segment = VIP**. No signals are computed. This is the kill-switch for protecting your top customers from false positives.

### Step 2: Check the Full-Confidence Order Threshold

If the customer has fewer counted orders than the **Full-confidence Order Threshold** (**Settings → General**, default 3), the calculation does *not* stop at 50. TrustLens records a zero-point system signal with the reason *"Limited local history (1/3 orders); return risk is confidence-weighted"* and then scores the customer with reduced inputs:

- Most detection modules (orders, coupons, shipping, chargebacks, card testing) are skipped until the customer reaches the threshold
- The **Returns** module still runs, with its penalty scaled down by a sample-size confidence factor, so one refund on one order never carries the weight of a long pattern
- The **account-age bonus** and **linked-account penalties** still apply

So a new customer's score — and segment — can move before they reach the threshold; they are not frozen at Normal. In Pro, confident external evidence (a Store Trust Network flag from one of your other stores) can switch on full scoring even for a first order.

This keeps new customers from being mis-classified as risky on the basis of one refund. Stores with strong signal quality can lower the threshold to 2; stores with very generous return policies sometimes raise it to 5.

### Step 3: Collect Signals from Active Modules

TrustLens iterates through every enabled detection module and calls its `get_signal()` method. Each module returns:

```
{
  score: integer (typically -50 to +20),
  reason: human-readable string ("6 of 9 eligible orders were refunded in the scoring window (67%; store baseline 8.0%; confidence 83%)")
}
```

Signals with both a zero score *and* an empty reason are dropped. Anything else is kept and appended to the customer's signal list.

### Step 4: Add Account-Age Bonus

Based on days since the customer's first order:

| Tenure | Bonus | Reason Text |
| --- | --- | --- |
| 1+ year (365+ days) | +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 bonus signal recorded) |

The bonus appears in the signals list under the `account_age` module so you can see its contribution explicitly.

### Step 5: Apply Filter Hook

Before summing, TrustLens runs the signals array through the `trustlens/score_signals` filter. TrustLens itself uses this filter to add the Linked Accounts penalty. Developers can use it to add, remove, or modify signals from custom code without forking the plugin.

### Step 6: Sum and Clamp

All signal scores are summed and added to the base score of 50. The result is clamped to the range 0–100. Anything below 0 becomes 0; anything above 100 becomes 100. The final score is then passed through one more filter, `trustlens/trust_score`, in case an extension needs to override it.

### Step 7: Determine Segment

The final score is mapped to one of six segments using these default cutoffs:

| Score | Segment |
| --- | --- |
| 90–100 | VIP |
| 70–89 | Trusted |
| 50–69 | Normal |
| 30–49 | Caution |
| 10–29 | Risk |
| 0–9 | Critical |

There is no Settings screen for these cutoffs; developers can override them with the `trustlens/segment_thresholds` filter.

### Step 8: Persist and Fire Hooks

TrustLens writes the new score and segment to the customer's database row, replaces the customer's signal log with the fresh signals (one row per signal), and fires two action hooks:

- `trustlens/score_updated` — always
- `trustlens/segment_changed` — only if the segment actually changed

Pro automation rules subscribe to these hooks; that's how a rule like *"if segment changed to Critical, send Slack alert"* fires.

---

## A Worked Example

Consider a customer named Sarah:

- 14 counted orders worth $2,600 in total; her most recent order was four months ago
- First order 8 months ago
- 5 refunded orders (36% return rate), all small partial refunds
- Total refund value $1,200 (46% of what she spent)
- 2 of those 5 refunded orders had used a coupon, and one of them was her first order with a first-order coupon
- 0 disputes, no linked accounts
- Store-wide return rate (the baseline): 8%

Walking through the formula:

| Step | Source | Contribution | Running Total |
| --- | --- | --- | --- |
| Base | — | +50 | 50 |
| Returns module | No orders in the last 90 days, so her lifetime history is scored. 36% is below the 40% high threshold but at least 30% (and at least 2× the 8% store baseline), so the high tier applies (20 raw points); refund value at 46% of purchases adds 5 raw points. 25 raw points × 0.94 sample-size confidence (14 orders), default 1.0× product multiplier | -23 | 27 |
| Coupons module | 2 refunded orders that used a coupon | -15 | 12 |
| Coupons module | First-order coupon + refunded coupon order present | -10 | 2 |
| Orders module | 9 clean orders (14 − 5 refunded) — 5+ clean tier | +10 | 12 |
| Orders module | Net value $1,400 ($2,600 − $1,200) — $1,000+ tier | +5 | 17 |
| Chargebacks module | 10+ counted orders and no disputes | +10 | 27 |
| Account age | 6+ months tenure | +10 | 37 |
| Clamp + segment | — | — | **37 → Caution** |

Sarah lands in Caution. A third refunded coupon order would push her into Risk; a clean stretch of orders without coupons would dilute her rates and gradually move her toward Normal.

The Customer Detail page shows this same calculation: every row above appears as a signal with its score and reason, so any team member can reproduce the result by hand.

---

## When Scores Recalculate

TrustLens never recalculates synchronously during a customer-facing request. Instead, relevant events queue an Action Scheduler job:

| Event | Triggers Recalculation |
| --- | --- |
| Order completed (or fully refunded) | Yes |
| Order cancelled | Yes |
| Order refunded (partial or full) | Yes |
| Order placed with a first-order coupon or several coupons | Yes |
| Dispute filed (Stripe/WooPayments webhook) | Yes |
| Dispute manually recorded, or its outcome marked won/lost | Yes |
| Linked account detected | No separate job — the link is counted at the customer's next recalculation (for example when the order completes) |
| Card-testing event tied to customer fingerprint | No separate job — picked up at the next recalculation |
| Shipping anomaly detected | Yes |
| Manual recalculate button clicked | Yes (immediate) |
| Allowlist turned on | No recalculation needed — the score is set to 100 / VIP immediately |
| Allowlist turned off | Yes (queued immediately) |
| Customer profile viewed | No — read-only |

### Deduplication

If the same customer triggers multiple recalculation requests in a short window (e.g. placing five orders within a minute), TrustLens collapses them into a single job: each new request replaces the pending one rather than adding another. This keeps Action Scheduler queues efficient and avoids redundant database writes.

### Latency

Jobs are scheduled with a short delay — about 60 seconds for order and refund events, 30 seconds for coupon events and 10 seconds for disputes — and then run when Action Scheduler next processes its queue, so score updates typically land within 1–2 minutes of the triggering event. If you need a score updated immediately, use the **Recalculate** button on the Customer Detail page — that runs synchronously.

---

## What Scoring Does Not Use

To set expectations clearly:

- **No external data.** TrustLens does not call IP geolocation services, email reputation services, or any third-party fraud API by default. All scoring is local. (The optional Pro Store Trust Network exchanges hashed risk flags only between stores you own.)
- **No machine learning.** The scoring is rule-based and deterministic. The same inputs always produce the same output.
- **No absolute refund-amount weighting.** Refund value counts relative to what the customer spent (the Returns module's refund-to-purchase ratio), not in raw currency, so a customer who refunds $10,000 of $20,000 is treated like one who refunds $100 of $200. The only absolute-value signal is the Orders module's +5 for $1,000+ net spend.
- **No demographic data.** Age, gender, region — none of these are inputs.
- **No identity verification.** Email format checks, disposable email detection, and similar are out of scope; TrustLens focuses on *behavior*.

---

## Inspecting a Calculation

Three places show the inputs to every score:

### 1. The Customer Detail Page

On the profile, the **Signal impact** panel lists every signal that contributed, with module name, score delta, and reason text. The numbers shown there are the exact ones used in the formula.

### 2. The Database

The `{prefix}trustlens_signals` table holds one row per signal per customer, refreshed on each recalculation. Developers can query it directly for offline analysis.

### 3. The REST API

`GET /wp-json/trustlens/v1/customers/{email_hash}` returns the score, segment, and the customer's order and refund counters (the individual signals are not part of the API response — read them on the Customer Detail page or from the signals table). See [REST API Reference](/docs/trustlens/developer-documentation/rest-api-reference/).

---

## Customizing the Calculation

For developers who need to extend scoring without forking:

| Hook | When It Fires | Use Case |
| --- | --- | --- |
| `trustlens/score_signals` (filter) | After modules emit signals, before summing | Add custom signals, remove a module's signal, modify reason text |
| `trustlens/trust_score` (filter) | After summing and clamping | Apply a global multiplier, force a minimum, or override based on external data |
| `trustlens/segment_thresholds` (filter) | When mapping score → segment | Shift thresholds (e.g. tighter VIP at 95) |
| `trustlens/score_updated` (action) | After score is persisted | Custom side effects — sync to CRM, log to external analytics |
| `trustlens/segment_changed` (action) | Only when segment actually transitions | Send "promoted to VIP" / "moved to Critical" notifications |

See [Hooks and Filters Reference](/docs/trustlens/developer-documentation/trustlens-hooks-and-filters/) for full signatures.

---

## Why This Design

The scoring engine intentionally favors transparency over sophistication. Trade-offs:

- **Deterministic over adaptive.** A machine learning model might catch subtler patterns, but you couldn't explain its decisions to a customer service team or a customer disputing a block. Rule-based scoring is explainable by design.
- **Sum over weighted average.** A weighted average would smooth out signals; summing preserves the magnitude of each contribution so a single severe signal (large dispute, very high return rate) can dominate when it should.
- **Clamp at the end, not per signal.** Clamping per signal would hide the true magnitude of compound abuse. Letting signals stack to -120 internally, then clamping to 0, ensures a customer with multiple severe signals reads as Critical, not just Caution.
- **Filters at every join.** Every step of the pipeline has a hook so the engine can be customized without forking the plugin.

The result is a scoring engine that's auditable, reproducible, and easy to reason about — which matters more than algorithmic sophistication when you're using the output to make real customer-facing decisions.
