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

# Risk signals

> Ingest fraud-detection signals from any source, normalize identity events into scored signals, and query them by entity, score, or type.

Risk signals let you feed fraud-detection data into Truthlocks from any upstream provider — device fingerprinting services, IP reputation APIs, email verification tools, document analysis engines, or your own internal rules. Once ingested, signals are available through the API and the **Risk & Fraud > Signals** page in the console.

<Info>
  The Anti-Fraud Identity Firewall is **generally available**. All five detection paths — direct signal ingestion, event normalization, deepfake scanning, ATO detection, and velocity scoring — are production-ready with multi-language API examples (cURL, JavaScript, Python), end-to-end guides, and real-time [webhook notifications](/guides/webhooks) for all signal types. Automated [risk decisions](/api-reference/risk/evaluate), [case management](#case-management), and [fraud SIEM export](#siem-export) are now available.
</Info>

You can create signals in five ways:

* **Direct ingestion** — send a fully scored signal via `POST /v1/risk/signals` when you already have a risk score from an external provider. Supports idempotent retries via the `Idempotency-Key` header.
* **Event normalization** — send a raw identity event via `POST /v1/risk/events` and let the platform map it to a signal type and base score automatically. See [event normalization](#event-normalization).
* **Deepfake and impersonation scanning** — submit subjects for automated analysis via `POST /v1/risk/deepfake/scan`. Scans that score 60 or above automatically ingest a risk signal. See the [deepfake detection guide](/guides/deepfake-detection).
* **Account takeover detection** — evaluate login events against velocity-based heuristics via `POST /v1/risk/ato/evaluate`. Alerts are created and risk signals ingested automatically when thresholds are crossed. See the [ATO detection guide](/guides/ato-detection).
* **Velocity and anomaly scoring** — record subject actions via `POST /v1/risk/velocity/record` and have the platform track frequency across rolling time windows (1 m, 5 m, 1 h, 24 h). Actions that produce a weighted velocity score of 60 or above automatically ingest a risk signal. See the [velocity scoring guide](/guides/velocity-scoring).

### Choosing an ingestion path

| Scenario                                                                                  | Use                                          | Endpoint                        |
| :---------------------------------------------------------------------------------------- | :------------------------------------------- | :------------------------------ |
| You have a risk score from an external fraud tool                                         | Direct ingestion                             | `POST /v1/risk/signals`         |
| You want platform-native events (failed logins, deepfake detections) scored automatically | Event normalization                          | `POST /v1/risk/events`          |
| You want to scan a subject for deepfake manipulation or impersonation                     | Deepfake scan                                | `POST /v1/risk/deepfake/scan`   |
| You want to detect account takeover attempts based on login velocity                      | ATO detection                                | `POST /v1/risk/ato/evaluate`    |
| You want to detect velocity anomalies across any action type                              | Velocity scoring                             | `POST /v1/risk/velocity/record` |
| You use multiple signal sources                                                           | All five — signals land in the same pipeline | Any endpoint                    |

All five paths produce the same risk signal records. You can query, filter, and review signals from any path using the [list endpoint](/api-reference/risk-signals/list) and the **Risk & Fraud > Signals** console page.

## How it works

<Steps>
  <Step title="Ingest signals">
    Send a `POST` request to `/v1/risk/signals` with the signal source, type, risk score, and the subject it relates to. Attach any extra context in the `payload` object.
  </Step>

  <Step title="Query and filter">
    Use `GET /v1/risk/signals` to list signals with filters for source, signal type, subject, and minimum score. Results are paginated with cursor-based pagination.
  </Step>

  <Step title="Review in the console">
    Open **Risk & Fraud > Signals** in the tenant console to browse signals, filter by source or score, and inspect individual signal details.
  </Step>
</Steps>

## Prerequisites

* An active Truthlocks tenant with an API key
* At least one upstream fraud-detection source that produces scored signals

## Ingesting a signal

Send a `POST` request with the required fields:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.truthlocks.com/v1/risk/signals \
    -H "X-API-Key: tl_live_..." \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{
      "signal_source": "external",
      "signal_type": "velocity",
      "risk_score": 85,
      "subject_type": "user",
      "subject_id": "usr_8f14e45f",
      "payload": {
        "ip": "203.0.113.42",
        "country": "US",
        "reason": "multiple_accounts_same_device"
      },
      "ip_address": "203.0.113.42"
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.truthlocks.com/v1/risk/signals", {
    method: "POST",
    headers: {
      "X-API-Key": "tl_live_...",
      "Content-Type": "application/json",
      "Idempotency-Key": crypto.randomUUID(),
    },
    body: JSON.stringify({
      signal_source: "external",
      signal_type: "velocity",
      risk_score: 85,
      subject_type: "user",
      subject_id: "usr_8f14e45f",
      payload: {
        ip: "203.0.113.42",
        country: "US",
        reason: "multiple_accounts_same_device",
      },
      ip_address: "203.0.113.42",
    }),
  });
  const signal = await response.json();
  ```

  ```python Python theme={null}
  import httpx
  import uuid

  resp = httpx.post(
      "https://api.truthlocks.com/v1/risk/signals",
      headers={
          "X-API-Key": "tl_live_...",
          "Idempotency-Key": str(uuid.uuid4()),
      },
      json={
          "signal_source": "external",
          "signal_type": "velocity",
          "risk_score": 85,
          "subject_type": "user",
          "subject_id": "usr_8f14e45f",
          "payload": {
              "ip": "203.0.113.42",
              "country": "US",
              "reason": "multiple_accounts_same_device",
          },
          "ip_address": "203.0.113.42",
      },
  )
  signal = resp.json()
  ```
</CodeGroup>

### Required fields

| Field           | Type    | Description                                                                                                              |
| :-------------- | :------ | :----------------------------------------------------------------------------------------------------------------------- |
| `signal_source` | string  | Origin of the signal — `verification`, `login`, `attestation`, `external`, or `manual`                                   |
| `signal_type`   | string  | What was detected (e.g. `velocity`, `geo_anomaly`, `device_fingerprint`, `behavior`, `deepfake`, `ato`, `impersonation`) |
| `risk_score`    | integer | Risk score from `0` (safe) to `100` (high risk). Scores at or above 80 trigger automatic review.                         |
| `subject_type`  | string  | Type of entity: `user`, `issuer`, `attestation`, `session`, `ip`, or `device`                                            |
| `subject_id`    | string  | Identifier of the entity being evaluated                                                                                 |

### Optional fields

| Field        | Type   | Description                                                                      |
| :----------- | :----- | :------------------------------------------------------------------------------- |
| `payload`    | object | Arbitrary JSON with signal-specific context (IP addresses, geolocation, reasons) |
| `ip_address` | string | Source IP address associated with this event                                     |
| `user_agent` | string | User-Agent string associated with this event                                     |

### Idempotency

Include an `Idempotency-Key` header (UUID) to prevent duplicate signals during retries. If you send the same key twice, the duplicate is silently ignored.

## Querying signals

### List with filters

```bash theme={null}
curl "https://api.truthlocks.com/v1/risk/signals?source=external&min_score=50&limit=25" \
  -H "X-API-Key: tl_live_..."
```

Available filters:

| Parameter      | Description                     |
| :------------- | :------------------------------ |
| `source`       | Filter by signal source         |
| `signal_type`  | Filter by signal type           |
| `subject_type` | Filter by subject type          |
| `subject_id`   | Filter by subject ID            |
| `min_score`    | Minimum score threshold (0–100) |
| `limit`        | Results per page (max 100)      |
| `cursor`       | Cursor for next page            |

### Get a single signal

```bash theme={null}
curl https://api.truthlocks.com/v1/risk/signals/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \
  -H "X-API-Key: tl_live_..."
```

## Console view

The **Risk & Fraud > Signals** page in the console provides a searchable, filterable list of all ingested signals. Each row shows the source, type, score (color-coded by severity), subject reference, and timestamp.

Use the filters at the top of the page to narrow results by source, signal type, or minimum score. Click any row to view the full signal details including the `payload` metadata object.

## Scoring guidelines

Consistent scoring across sources makes downstream analysis more reliable. We recommend the following ranges:

| Range    | Interpretation | Example                               |
| :------- | :------------- | :------------------------------------ |
| 0 – 30   | Low risk       | Known good device, reputable IP       |
| 30 – 60  | Moderate risk  | VPN usage, new account velocity       |
| 60 – 80  | High risk      | Disposable email, datacenter IP       |
| 80 – 100 | Critical risk  | Known fraud device, impossible travel |

<Warning>
  Scores at or above 80 trigger automatic review decisions. Score interpretation beyond the automatic threshold is up to you — the platform stores and returns scores as-is and does not normalize across sources.
</Warning>

## Event normalization

Instead of manually constructing risk signals, you can submit raw identity events and let the platform normalize them into signals automatically. This is useful when you want to feed platform-native events — failed logins, invalid signatures, deepfake suspects — directly into the risk pipeline without mapping them to signal types yourself.

Send a `POST` request to `/v1/risk/events` with the event source, type, and subject:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.truthlocks.com/v1/risk/events \
    -H "X-API-Key: tl_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "event_source": "login",
      "event_type": "login.failed.repeated",
      "subject_id": "user_abc123",
      "ip_address": "198.51.100.42",
      "payload": { "attempt_count": 8, "window_seconds": 120 }
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.truthlocks.com/v1/risk/events", {
    method: "POST",
    headers: {
      "X-API-Key": "tl_live_...",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      event_source: "login",
      event_type: "login.failed.repeated",
      subject_id: "user_abc123",
      ip_address: "198.51.100.42",
      payload: { attempt_count: 8, window_seconds: 120 },
    }),
  });
  const result = await response.json();
  ```

  ```python Python theme={null}
  import httpx

  resp = httpx.post(
      "https://api.truthlocks.com/v1/risk/events",
      headers={"X-API-Key": "tl_live_..."},
      json={
          "event_source": "login",
          "event_type": "login.failed.repeated",
          "subject_id": "user_abc123",
          "ip_address": "198.51.100.42",
          "payload": {"attempt_count": 8, "window_seconds": 120},
      },
  )
  result = resp.json()
  ```
</CodeGroup>

The response includes both the raw `event_id` and the normalized `signal_id`, so you can trace the full pipeline from event to signal:

```json theme={null}
{
  "event_id": "2a4b6c8d-...",
  "signal_id": "9f3a1c2d-...",
  "event_type": "login.failed.repeated",
  "signal_type": "ato",
  "risk_score": 70,
  "normalized": true,
  "created_at": "2027-05-01T10:00:00Z"
}
```

When `normalized` is `true`, the event matched a known mapping. When `false`, the platform used the generic fallback.

### Built-in event mappings

The platform ships with pre-calibrated mappings for common identity events:

| Event type                     | Signal type  | Base score |
| :----------------------------- | :----------- | :--------- |
| `verification.failed`          | behavior     | 60         |
| `verification.invalid_sig`     | behavior     | 75         |
| `login.failed.repeated`        | ato          | 70         |
| `login.suspicious_geo`         | geo\_anomaly | 65         |
| `attestation.deepfake_suspect` | deepfake     | 85         |
| `session.hijack_suspect`       | ato          | 90         |

Unknown event types are accepted with signal type `behavior` and a base score of 10.

### Event fields

**Required:**

| Field          | Type   | Description                                                                                    |
| :------------- | :----- | :--------------------------------------------------------------------------------------------- |
| `event_source` | string | System that generated the event — `attestation`, `verification`, `login`, or `consumer_portal` |
| `event_type`   | string | Raw event name (e.g. `login.failed.repeated`, `attestation.deepfake_suspect`)                  |
| `subject_id`   | string | Identifier of the entity involved in the event                                                 |

**Optional:**

| Field          | Type   | Description                                                              |
| :------------- | :----- | :----------------------------------------------------------------------- |
| `event_ref_id` | string | External reference ID (attestation ID, session ID) for cross-referencing |
| `ip_address`   | string | Source IP address                                                        |
| `payload`      | object | Raw event payload for enrichment context                                 |

<Info>
  Use event normalization when your events originate from within the platform (logins, verifications, attestations). Use the direct [signal ingestion endpoint](/api-reference/risk/ingest-signal) when you already have a scored signal from an external source.
</Info>

## Tenant isolation

Risk signals are fully tenant-isolated using [row-level security (RLS)](/security/rls). Each signal is bound to the authenticated tenant at ingestion time, and queries can only return signals that belong to the calling tenant.

## Deepfake and impersonation detection

The [deepfake detection API](/guides/deepfake-detection) extends the risk signal pipeline with automated scanning for manipulated content and identity fraud. Submit images, videos, documents, or attestations for analysis, and scans that exceed the risk threshold automatically create a risk signal — no extra API call needed.

See the [deepfake and impersonation detection guide](/guides/deepfake-detection) for the full workflow, indicator reference, and code examples.

## Account takeover detection

The [ATO detection API](/guides/ato-detection) extends the risk signal pipeline with velocity-based login monitoring. Send login events to `POST /v1/risk/ato/evaluate` and the platform tracks failed attempts per subject in a rolling one-hour window. When a subject crosses a threshold (5, 10, or 20 failed logins), an alert is created and a risk signal is automatically ingested — no extra API call needed.

See the [account takeover detection guide](/guides/ato-detection) for the full workflow, threshold reference, and integration patterns.

## Velocity and anomaly scoring

The [velocity scoring API](/guides/velocity-scoring) extends the risk signal pipeline with multi-window action tracking and anomaly detection. Record any subject action via `POST /v1/risk/velocity/record` and the platform tracks frequency across four rolling windows (1 m, 5 m, 1 h, 24 h). A weighted velocity score is computed from the window counts, biased toward short windows for burst detection. When the score reaches 60 or above, a risk signal with `signal_type: "velocity"` is automatically ingested — no extra API call needed.

See the [velocity and anomaly scoring guide](/guides/velocity-scoring) for the full workflow, scoring model, and integration patterns.

## Webhook notifications

Risk signals created by any of the five detection paths can trigger real-time [webhook](/guides/webhooks) notifications. Subscribe to `risk.signal.created` or `risk.signal.escalated` to react to new signals without polling. This covers signals from direct ingestion, event normalization, deepfake scans, ATO alerts, and velocity anomalies.

See the [webhooks guide](/guides/webhooks#risk-signal-notifications) for payload examples and integration patterns.

## Usage metering

Anti-Fraud features are metered per billing cycle. Each detection path increments its own usage counter:

| Metric                       | Metered action                                      |
| :--------------------------- | :-------------------------------------------------- |
| `antifraud.risk_signals`     | Risk signals ingested or generated (all five paths) |
| `antifraud.deepfake_scans`   | Deepfake and impersonation scan requests            |
| `antifraud.velocity_records` | Velocity scoring action records                     |

Quotas vary by plan tier. You can check your current consumption with the [usage API](/api-reference/billing/usage) or in the console at **Settings > Billing > Usage** under the **Anti-Fraud** section. To verify whether a specific operation is allowed before performing it, use the [limit check endpoint](/api-reference/billing/entitlements).

```bash theme={null}
curl https://api.truthlocks.com/v1/billing/usage \
  -H "X-API-Key: tl_live_..."
```

The response includes all Anti-Fraud counters with `used`, `limit`, and `period` fields so you can monitor consumption programmatically.

<Info>
  See the [billing overview](/billing/overview#metered-products) for the full list of 16 metered products and their default rates.
</Info>

## What's next

* [Velocity and anomaly scoring guide](/guides/velocity-scoring)
* [Account takeover detection guide](/guides/ato-detection)
* [Deepfake and impersonation detection guide](/guides/deepfake-detection)
* [Ingest risk signal API reference](/api-reference/risk-signals/ingest)
* [Normalize identity event API reference](/api-reference/risk/normalize-event)
* [List risk signals API reference](/api-reference/risk-signals/list)
* [Get risk signal API reference](/api-reference/risk-signals/get)
* [Row-level security](/security/rls)
