# Suspect score

> How the suspect score is calculated from weighted signals, how levels are derived from your threshold, and why an unscored request is not a zero.

Last updated: 2026-09-17

The suspect score summarises how suspicious a session looks. It is simple on purpose: a sum of weights you can read, explain and change.

## How the score is calculated

Each signal that fires adds its weight once. The score is the sum.

```text Example
tor               14
automation         9
tampering          8
high_activity      6
----------------------
suspect_score     37     → high (threshold 30)
```

- The score is not a percentage and has no maximum.
- Every signal that fired is listed in `triggers` with its weight and confidence, heaviest first. A signal with weight `0` still appears.
- Weights come from your [risk weights](https://docs.fingerly.io/docs/risk-weights): a key's own profile if it has one, otherwise your organization's, otherwise the defaults.

## Levels

The level compares the score with your threshold, which is `30` by default.

| Level | When | With the default threshold |
| --- | --- | --- |
| `low` | The score is below half the threshold. | 0 to 14 |
| `medium` | The score is at least half the threshold. | 15 to 29 |
| `high` | The score is at least the threshold. | 30 and above |

An identification that reaches `high` also sends the [`visitor.suspect`](https://docs.fingerly.io/reference/webhooks/visitor-suspect) webhook.

## When a request is not scored

If the network lookup could not run, the request is answered but not scored: `state` is `unavailable`, and `suspect_score` and `suspect_level` are absent. Fingerly does not invent a score from half the evidence.

> **Warning:** An absent score is not a score of `0`. `0` means everything was checked and nothing fired. Absent means the check did not happen. Decide separately what your application does in that case.

```ts Handling an unscored request
if (event.suspect_score === null) {
  // Not scored: fall back to your own rules, or ask for a second factor.
} else if (event.suspect_level === 'high') {
  // …
}
```

Requests whose network lookup could not run are not billed.

## Confidence

Each trigger has a `confidence` of `low`, `medium` or `high`, which says how strong the evidence for that signal was. Confidence is reported, not multiplied into the weight: a high-confidence signal with weight `0` adds nothing. Use confidence when you write rules on individual triggers.

## Choosing actions

| Level | A common first policy |
| --- | --- |
| `low` | Allow. |
| `medium` | Add friction a real customer can pass, such as a second factor or a CAPTCHA. |
| `high` | Hold for review, limit what the session can do, or refuse. |

- [Signals reference](https://docs.fingerly.io/docs/signals): Every signal group and its default weight.
- [Risk weights](https://docs.fingerly.io/docs/risk-weights): Change what each signal is worth.
