> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tracenow.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Signals

> Every field your rules can evaluate — IP reputation, email quality, phone risk, device fingerprint, velocity counters, and event status.

Signals are the enriched data points tracenow computes for every event. When you write a
rule condition — `velocity.ip.failed_count_1h gt 5` — you are referencing a signal by its
exact field name. This page is the authoritative reference for every field a rule can use.

Rules are built from **conditions**: `{ field, operator, value }`. Supported operators are
`eq` (equals), `gt` (greater than), and `lt` (less than). See [Rules](/guides/rules) for the
full rule and policy model.

<Note>
  **Device signals** (`device.*`) require the Cloudflare integration to be active — the
  Worker injects `trace.min.js` which collects the fingerprint. Without it, `device.*` fields
  are null.

  **User-keyed signals** (`velocity.user.*`, `ip.is_new_country_for_user`,
  `ip.impossible_travel`, `device.is_new_for_user`) reach full fidelity only when you pass a
  real `subject_user_id` in the [in-app call](/guides/server-side). At the edge these signals
  fall back to using the submitted email as the account key, which is still useful but less
  precise.
</Note>

## ip.\*

IP reputation and geo signals. Evaluated on every event.

| Field                        | Type    | Description                                                                                                                                          | Example values  |
| ---------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| `ip.is_tor`                  | boolean | IP is a known Tor exit node                                                                                                                          | `true`, `false` |
| `ip.is_vpn`                  | boolean | IP is associated with a commercial VPN service                                                                                                       | `true`, `false` |
| `ip.is_proxy`                | boolean | IP is an open or anonymous proxy                                                                                                                     | `true`, `false` |
| `ip.is_hosting`              | boolean | IP belongs to a cloud or hosting provider (AWS, GCP, DO, etc.)                                                                                       | `true`, `false` |
| `ip.is_new_country_for_user` | boolean | Request originates from a country this account has never used before. Requires `subject_user_id` for full fidelity.                                  | `true`, `false` |
| `ip.impossible_travel`       | boolean | Successive requests from this account arrive from locations physically impossible to travel between in the elapsed time. Requires `subject_user_id`. | `true`, `false` |

<Note>
  The enrichment response also includes geo (`country`, `country_code`), AS info (`asn`, `name`,
  `type`), and fields like `is_bot`, `bot_name`, `is_anonymous`, and `is_harmful`. These are
  visible on individual traces in the dashboard but are not directly addressable in rule
  conditions — only the fields in the table above are.
</Note>

## email.\*

Email quality and risk signals. Evaluated when `email` is present in the event.

| Field                          | Type    | Description                                                                 | Example values   |
| ------------------------------ | ------- | --------------------------------------------------------------------------- | ---------------- |
| `email.is_disposable`          | boolean | Address belongs to a temporary or throwaway email provider                  | `true`, `false`  |
| `email.domain_age_days`        | number  | Age of the email domain in days                                             | `4012`, `3`, `0` |
| `email.email_randomness_score` | number  | How random/generated the local part looks (0–10). Higher = more suspicious. | `2`, `7`, `9`    |
| `email.suspicious_tld`         | boolean | Domain uses a TLD commonly associated with spam or abuse                    | `true`, `false`  |

**Common rule patterns:**

```
email.is_disposable eq true
email.domain_age_days lt 30
email.email_randomness_score gt 7
email.suspicious_tld eq true
```

## phone.\*

Phone risk signals. Evaluated when `phone` is present in the event (most relevant for `otp`
events in SMS pumping protection).

| Field                        | Type    | Description                                                         | Example values    |
| ---------------------------- | ------- | ------------------------------------------------------------------- | ----------------- |
| `phone.valid`                | boolean | Phone number passes format and reachability validation              | `true`, `false`   |
| `phone.is_voip`              | boolean | Number is a VoIP line (Google Voice, Twilio, etc.)                  | `true`, `false`   |
| `phone.is_burner`            | boolean | Number is associated with a disposable or burner service            | `true`, `false`   |
| `phone.carrier_country_risk` | string  | Risk level of the carrier's country (`"low"`, `"medium"`, `"high"`) | `"low"`, `"high"` |

**Common rule patterns:**

```
phone.valid eq false
phone.is_voip eq true
phone.is_burner eq true
phone.carrier_country_risk eq "high"
```

## device.\*

Device fingerprint signals. **Requires the Cloudflare integration** — the Worker injects
`trace.min.js`, which collects the fingerprint and attaches the device token to requests.
Without it, all `device.*` fields are null.

`device.is_new_for_user` additionally requires a `subject_user_id` (or falls back to email)
to know what "new for this account" means.

| Field                         | Type    | Description                                                                                                 | Example values  |
| ----------------------------- | ------- | ----------------------------------------------------------------------------------------------------------- | --------------- |
| `device.is_headless`          | boolean | Browser appears to be running in headless mode (Puppeteer, Playwright, etc.)                                | `true`, `false` |
| `device.automation_detected`  | boolean | Automation framework signals detected (webdriver flags, CDP, etc.)                                          | `true`, `false` |
| `device.is_new_for_user`      | boolean | Device fingerprint has not been seen before for this account. Requires `subject_user_id` for full fidelity. | `true`, `false` |
| `device.timezone_ip_mismatch` | boolean | Device's reported timezone does not match the timezone inferred from the IP                                 | `true`, `false` |

<Note>
  The `/validate` response also includes `visitor_id` and `canvas_randomized` — these are
  visible on individual traces but are not directly addressable in rule conditions.
</Note>

## velocity.ip.\*

Request counters keyed by IP address. Measure activity from a single IP across all users and
accounts.

| Field                         | Type   | Description                                     | Example values    |
| ----------------------------- | ------ | ----------------------------------------------- | ----------------- |
| `velocity.ip.trace_count_5m`  | number | Total events from this IP in the last 5 minutes | `1`, `12`, `200`  |
| `velocity.ip.trace_count_1h`  | number | Total events from this IP in the last hour      | `3`, `50`, `1400` |
| `velocity.ip.failed_count_1h` | number | Failed events from this IP in the last hour     | `0`, `6`, `80`    |

`velocity.ip.failed_count_1h` combined with `event.status eq "failed"` is the canonical
credential stuffing signal.

## velocity.user.\*

Request counters keyed by user account. Requires a `subject_user_id` (or falls back to email)
to identify the account.

| Field                            | Type   | Description                                                                            | Example values   |
| -------------------------------- | ------ | -------------------------------------------------------------------------------------- | ---------------- |
| `velocity.user.trace_count_5m`   | number | Total events for this account in the last 5 minutes                                    | `1`, `4`, `30`   |
| `velocity.user.trace_count_1h`   | number | Total events for this account in the last hour                                         | `2`, `15`, `100` |
| `velocity.user.failed_count_1h`  | number | Failed events for this account in the last hour                                        | `0`, `5`, `40`   |
| `velocity.user.distinct_ips_24h` | number | Distinct IP addresses that have generated events for this account in the last 24 hours | `1`, `3`, `20`   |

`velocity.user.distinct_ips_24h gt 3` is a strong account takeover signal — a real user
rarely logs in from four different IPs in a day.

## velocity.device.\*

Request counters keyed by device fingerprint. Detects a single device spreading across
multiple accounts — a pattern common in fake-account factories and account takeover rings.

| Field                                   | Type   | Description                                                                   | Example values |
| --------------------------------------- | ------ | ----------------------------------------------------------------------------- | -------------- |
| `velocity.device.distinct_user_ids_24h` | number | Distinct user accounts (or emails) seen from this device in the last 24 hours | `1`, `4`, `30` |

`velocity.device.distinct_user_ids_24h gt 3` means the same physical device (or browser
profile) has touched more than three accounts today — a strong fake-account signal.

## event.status

The outcome of the event. Evaluated on every event, regardless of which other signals are
present.

| Field          | Type   | Description                                                       | Example values                           |
| -------------- | ------ | ----------------------------------------------------------------- | ---------------------------------------- |
| `event.status` | string | Outcome of the event: `"attempted"`, `"succeeded"`, or `"failed"` | `"attempted"`, `"succeeded"`, `"failed"` |

`event.status` is always used in combination with other signals. Status alone rarely justifies
a verdict — it tells you *what happened*, and the other signals tell you *why it's suspicious*.

See [Events](/concepts/events) for how status is determined at the edge vs. in the in-app call.

## Signal availability by call origin

| Signal group                 | Edge (Cloudflare Worker)                                    | In-app call (`/trace`)                                     |
| ---------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------- |
| `ip.*`                       | Full                                                        | Full                                                       |
| `email.*`                    | Full                                                        | Full                                                       |
| `phone.*`                    | Full                                                        | Full                                                       |
| `device.*`                   | Full (Worker injects the script)                            | Full (pass `device_token` from `x-tn-device-token` header) |
| `velocity.ip.*`              | Full                                                        | Full                                                       |
| `velocity.user.*`            | Falls back to email as account key                          | Full with `subject_user_id`                                |
| `velocity.device.*`          | Full                                                        | Full                                                       |
| `ip.is_new_country_for_user` | Falls back to email                                         | Full with `subject_user_id`                                |
| `ip.impossible_travel`       | Falls back to email                                         | Full with `subject_user_id`                                |
| `device.is_new_for_user`     | Falls back to email                                         | Full with `subject_user_id`                                |
| `event.status`               | `attempted` at entry; `succeeded`/`failed` in outcome trace | Exact — you set it directly                                |

## Signals in practice

<AccordionGroup>
  <Accordion title="Fake account signals (signup events)">
    The highest-signal combination for fake account detection:

    ```
    email.is_disposable eq true
    email.email_randomness_score gt 7
    email.domain_age_days lt 30
    email.suspicious_tld eq true
    ip.is_hosting eq true
    ip.is_tor eq true
    ip.is_proxy eq true
    ip.is_vpn eq true
    velocity.ip.trace_count_1h gt 10
    device.is_headless eq true
    device.automation_detected eq true
    velocity.device.distinct_user_ids_24h gt 3
    ```

    See [Fake accounts](/use-cases/fake-accounts) for example policies.
  </Accordion>

  <Accordion title="Account takeover signals (login events)">
    Credential stuffing, brute force, and suspicious access patterns:

    ```
    velocity.ip.failed_count_1h gt 5  AND  event.status eq "failed"
    velocity.user.failed_count_1h gt 5  AND  event.status eq "failed"
    device.is_headless eq true
    device.automation_detected eq true
    ip.is_tor eq true
    ip.is_proxy eq true
    ip.is_new_country_for_user eq true  AND  event.status eq "succeeded"
    ip.impossible_travel eq true
    velocity.user.distinct_ips_24h gt 3
    ip.is_vpn eq true
    device.is_new_for_user eq true  AND  event.status eq "succeeded"
    device.timezone_ip_mismatch eq true
    ```

    See [Account takeover](/use-cases/account-takeover) for example policies.
  </Accordion>

  <Accordion title="SMS pumping signals (otp events)">
    Stopping fraudulent OTP send requests before they cost you:

    ```
    phone.is_voip eq true
    phone.is_burner eq true
    phone.valid eq false
    phone.carrier_country_risk eq "high"
    velocity.ip.trace_count_5m gt 5
    velocity.user.trace_count_5m gt 3
    velocity.user.trace_count_1h gt 5
    ip.is_tor eq true
    ip.is_hosting eq true
    ip.is_proxy eq true
    device.is_headless eq true
    device.automation_detected eq true
    ```

    See [SMS pumping](/use-cases/sms-pumping) for example policies.
  </Accordion>
</AccordionGroup>

## Next steps

<Columns cols={2}>
  <Card title="Rules" icon="sliders" href="/guides/rules">
    Build conditions from these fields, combine them with AND/OR logic, and wire them into
    policies that return allow, challenge, or deny.
  </Card>

  <Card title="Verdicts" icon="gavel" href="/concepts/verdicts">
    What allow, challenge, and deny mean — and how to handle each in your app.
  </Card>
</Columns>
