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

# Webhooks & Payload Reference

> The risk.alert webhook payload, field by field

When a session crosses your configured risk threshold, InsightAI delivers a `risk.alert` event to your webhook URL (or over the live socket channel, for dashboard integrations). This page documents every field in that payload.

<Note>
  Not every field is populated on every alert — `entity.device.fingerprint_js` only applies to web sessions, `correlation` only appears when a device/visitor has meaningful cross-account history, and so on. Treat absent fields as "not applicable to this session," not as an error.
</Note>

## Full example payload

```json theme={null}
{
  "v": 1,
  "type": "risk.alert",
  "tier": "RISK_ONLY",
  "ts": "2025-12-15T10:20:30.123Z",
  "seq": 9103,
  "stream": {
    "org_id": "13237881",
    "tenant_id": "prod-ins2334974929"
  },
  "data": {
    "alert_id": "ALRT-20251215-9103",
    "decision": {
      "action": "BLOCK",
      "severity": "HIGH",
      "risk_score": 0.82,
      "confidence": 0.74
    },
    "entity": {
      "user": {
        "client_user_id": "u_123",
        "account_id": "acc_991",
        "labels": ["new_user", "high_value"]
      },
      "device": {
        "device_id": "d_4OwdmtpInKd9fW1fMBSAHN",
        "browser": {
          "name": "Chrome",
          "version": "142.0.0.0",
          "user_agent": "Mozilla/5.0 (...) Chrome/142.0.0.0 Safari/537.36"
        },
        "os": { "name": "MacOS", "version": "14.2" },
        "screen": { "resolution": "1792x1120", "pixel_ratio": 2, "color_depth": 24 },
        "hardware": { "platform": "MacIntel", "device_memory_gb": 8, "hardware_concurrency": 8 },
        "graphics": {
          "webgl_vendor": "Google Inc.",
          "webgl_renderer": "ANGLE (Intel, Intel Iris Plus Graphics)",
          "webgl_hash": "webgl_h_f19c9a8"
        },
        "fingerprint_js": {
          "provider": "fingerprintjs_oss",
          "visitor_id": "FpJS-7b8c9a23f92c4e1",
          "confidence_score": 0.63,
          "components_hash": "fpjs_comp_91ac",
          "bot_signals": {
            "suspected": true,
            "signals": ["low_input_entropy", "timing_regularities"]
          }
        }
      },
      "network": {
        "ip": "203.0.113.10",
        "geo": { "country": "IN", "region": "DL", "city": "New Delhi" },
        "asn": { "number": "AS9498", "isp": "Airtel", "type": "ISP" },
        "connection": {
          "vpn_proxy_suspected": true,
          "proxy_type": "residential_vpn",
          "effective_type": "4g",
          "rtt_ms": 12
        }
      }
    },
    "session": {
      "session_id": "SESS-1765265709938-uuuc7n",
      "first_seen_ts": "2025-12-15T10:19:50.000Z",
      "last_seen_ts": "2025-12-15T10:20:30.000Z",
      "navigation": { "route": "/checkout", "referrer": "/store" },
      "interaction": {
        "mouse_events": 412,
        "keyboard_events": 38,
        "touch_events": 0,
        "avg_event_interval_ms": 120,
        "entropy_score": 0.12
      }
    },
    "analytics": {
      "velocity": { "events_per_min": 27, "threshold": 20, "burst_detected": true },
      "reuse": {
        "visitor_id_reuse_count_24h": 3,
        "device_reuse_count_24h": 2,
        "ip_reuse_count_10m": 9
      },
      "automation": { "mouse_entropy": 0.12, "timing_variance": 0.04, "automation_likelihood": 0.86 }
    },
    "correlation": {
      "device_seen_on_users": [
        { "device_id": "d_4OwdmtpInKd9fW1fMBSAHN", "users": ["u_123", "u_881", "u_442"] }
      ],
      "visitor_id_seen_on_users": [
        { "visitor_id": "FpJS-7b8c9a23f92c4e1", "users": ["u_123", "u_881"] }
      ],
      "shared_ip_sessions": { "window": "10m", "count": 9 }
    },
    "flags": ["VPN_SUSPECTED", "MULTI_ACCOUNT_DEVICE", "VISITOR_ID_REUSE", "HIGH_VELOCITY", "AUTOMATION_LIKELY"],
    "reasons": [
      {
        "code": "STAT_HYPER_VELOCITY_SPIKE",
        "weight": 0.35,
        "evidence": { "events_per_min": 27, "threshold": 20 }
      },
      {
        "code": "STAT_LOW_ENTROPY_MOUSE",
        "weight": 0.22,
        "evidence": { "entropy": 0.12, "expected_min": 0.8 }
      }
    ],
    "client_action_hint": {
      "recommended_controls": ["3DS", "OTP", "KYC_STEP_UP"],
      "ttl_seconds": 900,
      "note": "Block purchase attempt; device shows automation & abnormal velocity."
    },
    "additional_data": {
      "order_id": "ORD-9912",
      "payment_attempt_id": "PAY-12001"
    }
  }
}
```

<Warning>
  If you copied this payload from an earlier version of this doc: the `stream` object needs a trailing comma before `"data"` — a bare object followed directly by another key without a comma is invalid JSON. The example above is corrected.
</Warning>

## Envelope fields

<ResponseField name="v" type="integer">
  Payload schema version. Check this before parsing if you maintain multiple integration versions — a version bump signals a field addition or shape change.
</ResponseField>

<ResponseField name="type" type="string">
  Always `risk.alert` for this event. Reserved for future event types (e.g. `case.updated` from the AML side).
</ResponseField>

<ResponseField name="tier" type="string">
  `RISK_ONLY` for Device Intelligence alerts. Distinguishes this stream from AML case-investigation events on shared infrastructure.
</ResponseField>

<ResponseField name="ts" type="string">
  ISO-8601 timestamp of when this event was generated — not necessarily when the underlying session activity occurred.
</ResponseField>

<ResponseField name="seq" type="integer">
  Monotonically increasing sequence number per tenant stream. Use this to detect gaps (missed deliveries) or out-of-order delivery, not `ts` — clock skew makes `ts` unreliable for ordering.
</ResponseField>

<ResponseField name="stream" type="object">
  <Expandable title="properties">
    <ResponseField name="org_id" type="string">Your organization ID.</ResponseField>
    <ResponseField name="tenant_id" type="string">Your tenant slug/ID within that org.</ResponseField>
  </Expandable>
</ResponseField>

## data.decision

<ResponseField name="action" type="string">
  `PASS` / `REVIEW` / `BLOCK` — the recommended action. Same three-tier model as RASP's on-device scoring (see [RASP — Scoring & decisions](/device-intelligence/rasp#scoring-and-decisions)), but computed server-side with the full graph and analytics context RASP alone doesn't have.
</ResponseField>

<ResponseField name="severity" type="string">
  `LOW` / `MEDIUM` / `HIGH` / `CRITICAL` — independent of `action`; a `REVIEW` can still carry `HIGH` severity if you want severity-based routing separate from the action itself.
</ResponseField>

<ResponseField name="risk_score" type="number">
  0–1. Note this is a different scale from RASP's 0–100 `rasp_score` — don't conflate the two when building unified dashboards.
</ResponseField>

<ResponseField name="confidence" type="number">
  0–1. How confident the model is in this decision, independent of how risky it is — a high-risk, low-confidence alert is a good candidate for `REVIEW` rather than an automatic `BLOCK`.
</ResponseField>

## data.entity.device

The device fingerprint block. `fingerprint_js` only appears for web sessions (see [Beyond RASP — web fingerprinting](/device-intelligence/beyond-rasp#web-fingerprinting-browser-sessions)); native app sessions carry the SDK's own `device_id` and RASP signal set instead, delivered on the [telemetry ingest](/api-reference/endpoint/di/ingest_telemetry) payload rather than here.

<Expandable title="fingerprint_js.bot_signals">
  <ResponseField name="suspected" type="boolean">Whether the fingerprinting provider itself flagged bot-like signals, independent of InsightAI's own automation scoring in `analytics.automation`.</ResponseField>
  <ResponseField name="signals" type="string[]">Specific indicators — e.g. `low_input_entropy`, `timing_regularities`. Provider-specific; treat as informational context, not a field to build hard logic against.</ResponseField>
</Expandable>

## data.analytics

The three sub-objects here are the "beyond RASP" signals described conceptually in [Beyond RASP](/device-intelligence/beyond-rasp):

<ResponseField name="velocity" type="object">
  Events-per-minute against a rolling threshold, with `burst_detected` flagging sudden spikes rather than sustained high volume.
</ResponseField>

<ResponseField name="reuse" type="object">
  How many times this device/visitor/IP has appeared across **distinct accounts** within the stated window. High reuse counts are the core multi-accounting signal.
</ResponseField>

<ResponseField name="automation" type="object">
  `automation_likelihood` is the single composite score most integrations key off directly — it's already a combination of `mouse_entropy` and `timing_variance`, so you rarely need the two sub-components separately unless you're building your own weighting.
</ResponseField>

## data.correlation

Only populated when there's meaningful cross-account history — this is the device-to-user graph described in [Beyond RASP](/device-intelligence/beyond-rasp#device-to-user-graph-mapping), rendered as a snapshot at alert time.

<ResponseField name="device_seen_on_users" type="array">
  Each entry is one device and the full list of user IDs it's been seen on. A list longer than 1–2 unrelated users is the core multi-accounting signal.
</ResponseField>

<ResponseField name="visitor_id_seen_on_users" type="array">
  Same idea for the browser-fingerprint `visitor_id`, for web sessions.
</ResponseField>

<ResponseField name="shared_ip_sessions" type="object">
  Count of distinct sessions from the same IP within the stated window — useful for detecting a single physical location (or NAT/proxy exit point) driving many sessions, distinct from device- or visitor-level reuse.
</ResponseField>

## data.flags and data.reasons

Two related but distinct fields — **don't build logic against `flags` alone**:

<ResponseField name="flags" type="string[]">
  A flat list of everything that fired — good for tagging/filtering in your own systems (e.g. "show me all `VPN_SUSPECTED` alerts this week"), but carries no weight information.
</ResponseField>

<ResponseField name="reasons" type="array">
  The same signals, but with a `weight` (contribution to the overall risk score) and `evidence` (the actual numbers behind the flag). This is what you want for anything that explains *why* a decision was made — an analyst-facing UI, an audit trail, or a dispute-resolution flow.
</ResponseField>

<Tip>
  A common integration mistake: building a rules engine against `flags` (string matching) instead of `reasons` (structured, weighted). `flags` is for filtering; `reasons` is for reasoning about the decision.
</Tip>

## data.client\_action\_hint

<ResponseField name="recommended_controls" type="string[]">
  Suggested step-up controls — e.g. `3DS`, `OTP`, `KYC_STEP_UP`. Advisory, not enforced by InsightAI — your integration decides whether and how to apply them.
</ResponseField>

<ResponseField name="ttl_seconds" type="integer">
  How long this recommendation should be considered valid. Don't apply a stale hint to a much-later action on the same session.
</ResponseField>

<ResponseField name="note" type="string">
  A human-readable summary, suitable for direct display in an analyst console without further formatting.
</ResponseField>

## data.additional\_data

Pass-through fields you supplied at ingest time (e.g. `order_id`, `payment_attempt_id`) — echoed back unchanged so you can correlate the alert with your own transaction record without a separate lookup.

## Delivery and retries

Webhook delivery uses exponential backoff on non-2xx responses, up to a bounded number of attempts before an event is marked failed. Always verify the signature (see [Authentication](/api-reference/authentication#verifying-inbound-webhooks-insightai-your-backend)) and return a 2xx quickly — do heavier processing asynchronously rather than inside the webhook handler itself, since a slow handler risks the delivery being retried unnecessarily.
