---
title: "Identification scored"
method: POST
path: "identification.scored"
tags: ["Webhooks"]
---

# Identification scored

`POST identification.scored` (webhook)

Sent when an identification is complete. The Risk Score describes this identification. Verify HMAC-SHA256 over the exact raw body using the endpoint secret. Deduplicate by the signed event_id. Acknowledge durable acceptance with 2xx within the delivery timeout. Retries preserve the body and ID. Use History for the latest stored result. See https://docs.shieldlabs.ai/setup/webhooks for delivery and signature handling.

## Headers

- `X-Shield-Signature` string, required
- `X-Shield-Event-Id` string

## Payload

- IdentificationScoredEvent: Body of an `identification.scored` delivery. The signature is not part of the body: it arrives in the `X-Shield-Signature` header.
  - `event_type` 'identification.scored', required: Event type. Ignore events whose type you do not know instead of failing.
  - `schema_version` '2026-10-13', required: Schema version of this identification event.
  - `created_at` string, date-time, required: RFC 3339 timestamp in UTC with up to 9 fractional digits (trailing zeros trimmed), for example `2026-09-30T12:34:57.482913041Z`. Parse it with a parser that accepts nanoseconds.
  - `data` IdentificationScoredData, required: The completed identification: identifiers, Risk Score, signal contributions, twenty detection flags and account-event evaluations.
    - `client_identity` WebhookClientIdentity, nullable: Optional best-effort client attribution for webhooks. The object and every nested field may be omitted or null. Availability and completeness are not guaranteed; integrations must work without this block or any of its fields. Missing or null values do not mean human or a negative verification result. Claimed names are unverified; a verified provider does not prove a service or agent. Unknown fields and string values must be accepted.
      - `schema_version` string, nullable: Open version identifier. Version 1 is currently emitted.
      - `classification_revision` integer, nullable
      - `classifier_version` string, nullable
      - `registry_revision` string, nullable
      - `availability` string, nullable: available or input_unavailable. Missing object means no stored identity data; it does not mean human.
      - `observed_at` string, date-time, nullable
      - `decided_at` string, date-time, nullable
      - `claims` object[], nullable
        - `profile_id` string, nullable
        - `provider_id` string, nullable
        - `provider_name` string, nullable
        - `agent_name` string, nullable
        - `client_kind` string, nullable
        - `purpose` string, nullable
        - `source` string, nullable
      - `verified` object[], nullable
        - `subject` string, nullable: The proved attribute: provider, service or infrastructure. Proof does not extend to other attributes.
        - `value_id` string, nullable
        - `evidence_ids` string[], nullable
      - `assessments` object[], nullable
        - `candidate_profile_id` string, nullable
        - `status` string, nullable
        - `reason` string, nullable
      - `evidence` object[], nullable
        - `id` string, nullable
        - `method` string, nullable
        - `source_id` string, nullable
        - `source_revision` string, nullable
        - `checked_at` string, date-time, nullable
        - `evaluated_at` string, date-time, nullable
        - `covered_attributes` string[], nullable
        - `covered_components` string[], nullable
        - `request_binding` string, nullable
        - `replay_policy` string, nullable
    - `identification_id` string, uuid, required: Identifies one identification. The browser creates it as a UUID v4 and hands it to your page; it is the join key between the browser, the webhook and the History API. The nil UUID appears only on rate-limit marker rows that arrived with a malformed Identification ID.
    - `visitor_id` string, uuid, required: Server-side visitor identifier (UUID v5). It is sticky to the device: a new cookie on a known device keeps the existing visitor ID, so clearing cookies usually does not change it. The nil UUID appears on identifications without usable device data, such as rate-limit marker rows.
    - `device_id` string, uuid, required: Server-side device identifier (UUID v5). It survives cleared cookies and private windows. The nil UUID `00000000-0000-0000-0000-000000000000` means that no usable device signals were collected (for example on rate-limit marker rows): never group identifications by it.
    - `session_id` string, uuid, required: One visit on one origin (UUID v4 created in the browser), shared by the open tabs of that origin. The next visit after the last tab closes gets a new session ID. The nil UUID appears on rate-limit marker rows.
    - `cookie_id` string, uuid, required: First-party browser identifier kept by the ShieldLabs agent (UUID v4). A missing or malformed value is stored as the nil UUID.
    - `user_hid` string, nullable, required: User HID: your hashed or pseudonymous account identifier, exactly as it was passed to the ShieldLabs agent. Pass a hashed value, never a raw email address or database ID. Values that do not identify a user: - `anonymous`: an anonymous check; - `fail`: the agent sent no value; - `-1` and `unknown`: rows created by ShieldLabs itself, such as rate-limit marker rows. `null` only when the stored value is an empty string. Leave `null` and the values above out when you count the accounts of one device, visitor or IP address.
    - `domain` string, required: Registered domain of your site (the request host when no registered domain matched).
    - `public_ip` IpInfo, required: An IPv4 address and its country. Both keys are always present and can be empty strings.
      - `ip` string, required: Dotted IPv4 address, or an empty string when no IPv4 address is known (for example for visitors on IPv6).
      - `country` string, required: English country name from IP intelligence, for example `Germany` or `United States` (not an ISO code). Empty string when the country is unknown.
    - `local_ip` IpInfo, required: An IPv4 address and its country. Both keys are always present and can be empty strings.
      - `ip` string, required: Dotted IPv4 address, or an empty string when no IPv4 address is known (for example for visitors on IPv6).
      - `country` string, required: English country name from IP intelligence, for example `Germany` or `United States` (not an ISO code). Empty string when the country is unknown.
    - `connection_type` string, required: Network classification: direct, mobile, vpn, proxy, tor, private_relay or unknown. Read detection_flags for individual detections and risk_score for the identification verdict. The set is open; handle unrecognized values as unknown.
    - `os` string, required: Operating system name, for example `Windows`, `Mac OS X`, `Linux`, `Android`, `IOS (iPhone)`, `IOS (iPad)`, `ChromeOS` or `Unknown`. Open set: display it, do not branch on it.
    - `browser` string, required: Browser name, for example `Chrome`, `Safari`, `Firefox`, `Microsoft Edge`, `Opera`, `Samsung Internet`, `Brave`, `Chrome (iOS)`, `Safari (iOS)` or `Unknown`. Open set: display it, do not branch on it.
    - `device_type` string, required: Device class from the browser. Known values: `desktop`, `mobile`, `tablet` and `unknown` (the class could not be determined). The set is open: keep values added in later versions and treat them as `unknown`.
    - `traffic_source` TrafficSource, required: Where the visit came from. All nine keys are always present; values can be empty strings.
      - `channel` string, required: Marketing channel of the visit. Known values: `Google Ads`, `Meta`, `TikTok`, `LinkedIn`, `X`, `Pinterest`, `Microsoft Ads`, `Organic Search`, `Search bot`, `Referral`, `Direct`, `Other` and the empty string. Resolved in this order: a click ID, then UTM parameters, then the referrer (search engines give `Organic Search`, social networks give the platform name, other sites give `Referral`), otherwise `Direct`. Search-engine crawlers get `Search bot`. Empty string on identifications without attribution, such as rate-limit marker rows. The set is open: keep values added in later versions and treat them as `Other`.
      - `referrer_domain` string, required: Registrable domain of the referrer without `www.`. For search-engine crawlers, the crawler name (for example `GoogleBot`).
      - `landing_url` string, required: Landing page URL without the `#fragment`. It keeps the query string, which can contain personal data: store it with care.
      - `click_id_type` string, required: Ad click identifier found in the landing URL. Known values: `gclid`, `gbraid`, `wbraid`, `msclkid`, `ttclid`, `fbclid` and the empty string when there is none. `fbclid` counts only together with a Meta referrer or a Meta `utm_source`. The set is open: keep values added in later versions.
      - `utm_source` string, required: `utm_source` query parameter, lowercased.
      - `utm_medium` string, required: `utm_medium` query parameter, lowercased.
      - `utm_campaign` string, required: `utm_campaign` query parameter as sent.
      - `utm_content` string, required: `utm_content` query parameter as sent.
      - `utm_term` string, required: `utm_term` query parameter as sent.
    - `risk_score` integer, required: Risk Score from 0 (no risk found) to 100. Search-engine crawlers always score 0. Risk bands are computed on your side from the score; no risk band field exists on the wire: - trusted: 0-29 - suspicious: 30-59 - dangerous: 60-100 A value above 100 is not a score. `999` is the rate-limit marker: the visitor's IP went over the ingest rate limit, and the identification carries exactly one signal, `{"name":"banned_ip","weight":999}`, usually with nil identifiers. Treat every value above 100 as rate limited. One marker is written when the IP goes over the limit; Identification IDs issued while it stays blocked get no row and no webhook, so they stay unverified. The score usually equals the sum of the signal weights capped at 100, but carried-forward verdicts and corrections make that unreliable: never recompute or validate it yourself.
    - `signals` Signal[], required: Weighted risk signals behind `risk_score`, in scoring order. Can be empty. The rate-limit marker carries exactly one entry, `{"name":"banned_ip","weight":999}`.
      - `name` string, required: Public signal name. Known names: vpn, proxy, private_relay, check_incomplete, network_check_incomplete, os_not_detected, timezone_mismatch, javascript_disabled, os_mismatch, ip_mismatch, latency_test, antidetect_browser, device_spoofing, banned_ip, incognito, search_bot, ai_bot, browser_automation, ai_browser and tor. The set is open; preserve unknown names.
      - `weight` integer, required: Contribution recorded for this signal. Use the returned Risk Score rather than calculating a verdict from individual contributions.
    - `detection_flags` DetectionFlags, required: Twenty public detection flags. Read signals for score contributions. Provider attribution is returned in separate owner fields.
      - `vpn` boolean, required: A VPN was detected, including a VPN or proxy built into the browser.
      - `proxy` boolean, required: A proxy was detected.
      - `private_relay` boolean, required: A private relay was detected.
      - `check_incomplete` boolean, required: Part of the browser checks timed out, so the verdict rests on partial data. Informational. Weight 0, and present in signals when true. False for a search-engine crawler, an AI bot, or an AI browser.
      - `network_check_incomplete` boolean, required: The browser network check did not complete.
      - `os_not_detected` boolean, required: The operating system could not be determined from the User-Agent or the network.
      - `timezone_mismatch` boolean, required: The browser time zone is inconsistent with the public IP location.
      - `javascript_disabled` boolean, required: JavaScript, or the browser APIs the checks need, were unavailable. False for a search-engine crawler, an AI bot, or an AI browser.
      - `os_mismatch` boolean, required: The reported operating system is inconsistent with the detected operating system.
      - `ip_mismatch` boolean, required: A qualifying mismatch between the public IP and the browser's network evidence was detected. Nominal weight 45; read the returned Risk Score. False for a search-engine crawler, an AI bot, or an AI browser.
      - `latency_test` boolean, required: The latency test detected an indirect connection.
      - `anti_detect_browser` boolean, required: An anti-detect browser was detected.
      - `device_spoofing` boolean, required
      - `banned_ip` boolean, required: The public IP is banned.
      - `incognito` boolean, required: The browser runs in a private window. False for a search-engine crawler, an AI bot, or an AI browser. Weight 0, and present in signals when true.
      - `search_bot` boolean, required: A search-engine crawler. Its Risk Score is always 0. Weight 0, and present in signals when true.
      - `ai_bot` boolean, required: A recognized AI bot or crawler. Its Risk Score is always 0. ShieldLabs identifies the provider when available. Weight 0, and present in signals when true.
      - `browser_automation` boolean, required: Browser automation was detected. Signal weight: 90.
      - `ai_browser` boolean, required: A recognized AI browser. Its Risk Score is always 0. ShieldLabs identifies the provider when available. Weight 0, and present in signals when true.
      - `tor` boolean, required: The request came through the Tor network.
    - `observed_at` string, date-time, required: RFC 3339 timestamp in UTC with up to 9 fractional digits (trailing zeros trimmed), for example `2026-09-30T12:34:57.482913041Z`. Parse it with a parser that accepts nanoseconds.
    - `hre` HRE, required: Account-sharing, account-takeover and impossible-travel evaluations for this identification. Read each status before its confidence level. Multi-accounting is delivered as a separate group-change event.
      - `rules_version` string: Optional evaluation rules version. Store the returned identifier with the result.
      - `account_sharing` HREResult, required: One account-event evaluation. A missing verdict is different from an evaluated result with no active detection.
        - `status` string, required: evaluated means the evaluation completed; not_evaluated means no completed verdict is available; not_applicable means there is no tracked account for this check.
        - `level` string, nullable, required: medium or high for an active detection; otherwise null. Separate from the identification Risk Score.
        - `reason` string, required: Result context; can be empty. Preserve unknown values. no_history and skipped explain an incomplete evaluation; anonymous_user explains not_applicable.
        - `devices` integer: Device count reported for account sharing. Read devices_at_least to determine whether this is a lower bound.
        - `min_devices` integer: Minimum device count applied to this account-sharing evaluation. Use the returned value.
        - `cluster_id` string, nullable, required: Evidence cluster identifier when supplied; otherwise null. Separate from the Identification ID and Device ID.
        - `devices_at_least` boolean: When true, the account-sharing devices count is a lower bound. Can be omitted when false.
      - `account_takeover` HREResult, required: One account-event evaluation. A missing verdict is different from an evaluated result with no active detection.
        - `status` string, required: evaluated means the evaluation completed; not_evaluated means no completed verdict is available; not_applicable means there is no tracked account for this check.
        - `level` string, nullable, required: medium or high for an active detection; otherwise null. Separate from the identification Risk Score.
        - `reason` string, required: Result context; can be empty. Preserve unknown values. no_history and skipped explain an incomplete evaluation; anonymous_user explains not_applicable.
        - `devices` integer: Device count reported for account sharing. Read devices_at_least to determine whether this is a lower bound.
        - `min_devices` integer: Minimum device count applied to this account-sharing evaluation. Use the returned value.
        - `cluster_id` string, nullable, required: Evidence cluster identifier when supplied; otherwise null. Separate from the Identification ID and Device ID.
        - `devices_at_least` boolean: When true, the account-sharing devices count is a lower bound. Can be omitted when false.
      - `impossible_travel` HREResult, required: One account-event evaluation. A missing verdict is different from an evaluated result with no active detection.
        - `status` string, required: evaluated means the evaluation completed; not_evaluated means no completed verdict is available; not_applicable means there is no tracked account for this check.
        - `level` string, nullable, required: medium or high for an active detection; otherwise null. Separate from the identification Risk Score.
        - `reason` string, required: Result context; can be empty. Preserve unknown values. no_history and skipped explain an incomplete evaluation; anonymous_user explains not_applicable.
        - `devices` integer: Device count reported for account sharing. Read devices_at_least to determine whether this is a lower bound.
        - `min_devices` integer: Minimum device count applied to this account-sharing evaluation. Use the returned value.
        - `cluster_id` string, nullable, required: Evidence cluster identifier when supplied; otherwise null. Separate from the Identification ID and Device ID.
        - `devices_at_least` boolean: When true, the account-sharing devices count is a lower bound. Can be omitted when false.
    - `search_bot_owner` string: Owner label from accepted search-bot detection; omitted when unknown or inactive.
    - `ai_bot_owner` string: Provider company label from accepted AI-bot detection; omitted when unknown or inactive.
    - `ai_browser_owner` string: Owner label from accepted AI-browser detection; omitted when unknown or inactive.
  - `event_id` string, required: Stable logical event ID. Delivery retries preserve this ID and the signed body.
  - `site_id` integer: Site scope, when available.

## Acknowledgement `2XX`

Delivery accepted. The response body is ignored.

## Other responses

- `4XX`: Permanent 4xx are terminal; 429 is retried. 2xx acknowledges durable acceptance.

---

[API](https://skmtc.dev/shieldlabs/apis/shieldlabs-api.md) - [All operations](https://skmtc.dev/shieldlabs/apis/shieldlabs-api/llms.txt) - [OpenAPI document](https://skmtc.dev/shieldlabs/apis/shieldlabs-api/revisions/f8c7da1cd44d?raw)
