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

# Identification

> Learn how ShieldLabs recognizes a returning visitor and what each of the six identifiers is for.

Every visit resolves to six identifiers, and the most durable of them, the DeviceID, holds even after a visitor clears cookies or opens an incognito window.

You read the identity and its signals from the [webhook](/api/webhooks) or the [History API](/api/server-api).

## The identifier model

Every identify call returns six identifiers. They are ordered here by how durable and useful each one is, from the persistent **DeviceID** down to the per-call **RequestID**.

| Identifier    | How it is made                                                                                                    | Persistence                                                                       |
| ------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| **DeviceID**  | Server-derived; not stored in the browser.                                                                        | **Durable.** Survives cleared cookies, incognito, and IP rotation. Browser-bound. |
| **UserHID**   | Your own hashed account id, passed into the snippet. You set this value yourself.                                 | Whatever you set.                                                                 |
| **VisitorID** | Server-derived from the DeviceID combined with the CookieID.                                                      | Resets when cookies are cleared. Many VisitorIDs can map to one DeviceID.         |
| **CookieID**  | A first-party UUID minted in the browser and stored in cookies and site storage.                                  | Lost when cookies or site storage are cleared.                                    |
| **SessionID** | A client UUID for the current visit, kept in session storage and short-lived.                                     | Resets after a short window of inactivity, or in a new browser session.           |
| **RequestID** | A client-generated UUID, one per identify call. It joins the snapshot, the webhook, and the History API together. | Unique per call.                                                                  |

`RequestID`, `SessionID`, and `CookieID` are minted in the browser. `DeviceID` and `VisitorID` are computed on the server from the collected signals, so the browser never sees them. You correlate everything by `RequestID`.

### Identifier hierarchy

The six identifiers nest from a single request up to a known account:

* **RequestID** is one identify call.
* **SessionID** is one visit.
* **CookieID** is the browser's first-party storage.
* **VisitorID** is the DeviceID and CookieID together, the cookie-scoped identity.
* **DeviceID** is the durable, browser-bound device identity.
* **UserHID** is the signed-in account you pass in.

A single DeviceID can sit behind many VisitorIDs, many sessions, and many requests over time.

## Why DeviceID is durable

DeviceID is the identifier most analytics tools cannot match. The server computes it from hundreds of stable browser and device characteristics. The same browser environment always produces the same DeviceID. Because nothing about it lives in the cookie, it holds when cookie-based tracking breaks:

* **Survives cleared cookies.** Clearing cookies removes the CookieID, not the identity.
* **Survives incognito.** A private window keeps the same environment, so the DeviceID is stable.
* **Survives IP rotation.** Switching networks or using a VPN does not change the identity.

This durability is why a returning person keeps the same DeviceID, even after their cookies expire. It is also the foundation of [identification accuracy](/features/accuracy), where corroborating several independent signals takes anonymity detection up to 99%.

<Warning>
  DeviceID is **browser-bound**. The same person on Chrome and on Firefox produces two different DeviceIDs.
</Warning>

<Note>
  When the snippet is blocked or JavaScript is disabled, no stable characteristics reach the server, so the DeviceID comes back as the all-zero UUID `00000000-0000-0000-0000-000000000000`. A JavaScript-disabled visit scores **90**. Route a null or all-zero DeviceID to review rather than auto-allowing it: you cannot recognize a returning person from an identity that was never built.
</Note>

## Why VisitorID resets

VisitorID is built from two inputs: the DeviceID and the CookieID. The DeviceID half is durable. The CookieID half is not.

So when a visitor clears cookies:

1. The CookieID is gone, and the browser mints a fresh one on the next visit.
2. The server combines the same DeviceID with the new CookieID.
3. The result is a new VisitorID.

That is by design. One durable DeviceID can sit behind many VisitorIDs over time, and the VisitorID only ever moves forward: each cleared-cookie mints a fresh CookieID, so once a device leaves a VisitorID behind, that value does not reappear on it. So if a VisitorID a device already moved past shows up on that same device again (you saw `A`, then `B`, then `A` once more), that is physically impossible under normal use, and a strong sign the cookie was copied or stolen and replayed. This is a basic, built-in check for stolen or copied cookies.

Use `DeviceID` for the most stable handle on a returning person within a browser. Use `VisitorID` for the cookie-scoped view, and `UserHID` once a person signs in.

<Tip>
  A burst of new VisitorIDs all mapping to one DeviceID, or one VisitorID seen across many DeviceIDs, is itself a signal. The dashboard surfaces these as [Patterns](/features/patterns).
</Tip>

## UserHID is your hashed account id

UserHID is the only identifier you control. It is your own account id, passed into the snippet so ShieldLabs can tie anonymous activity to a known account.

Pass a hashed or pseudonymous value, and apply the same transform every time so the same account always maps to the same `UserHID`.

```js theme={null}
// Hash the account id before passing it to the snippet.
const userHID = await sha256(currentUser.id); // never the raw id or email
const mod = await import('https://cdn.shieldlabs.ai/snippet.js?publicKey=YOUR_PUBLIC_KEY');
mod.checkAuthenticatedUser(userHID);
```

<Warning>
  Never pass a raw email, username, or primary-key user id as the `UserHID`. ShieldLabs echoes the `UserHID` back in webhooks and History, so a raw value would put plaintext account data in those payloads. Always hash it or use a pseudonymous token.
</Warning>

When no one is signed in, call `checkAnonymous()` and the `UserHID` is left unset. The [snippet setup](/setup/snippet) documents the full method list.

## Device Intelligence is part of identification

The output of identification is not just an ID. It is the identity plus the signals around that device, delivered together in one response.

| You get                | What it is                                                                                                                         |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| **DeviceID**           | The persistent, browser-bound identity described above.                                                                            |
| **Device attributes**  | The visitor's `browser`, `os`, and `device_type` (`desktop`, `mobile`, or `tablet`).                                               |
| **Location**           | `country`, derived from the IP (geolocation), alongside the public IP and, when available, the local IP.                           |
| **Risk Score (0-100)** | An explainable score with a `signals` array naming every signal that fired, detailed under [Risk Scoring](/features/risk-scoring). |
| **Signals**            | The anonymity and mismatch signals behind the score, cataloged under [Anonymity Signals](/features/anonymity-signals).             |

You never call a separate "fingerprint" endpoint and a separate "risk" endpoint. One snippet collects the signals, the server scores them, and the result arrives by [webhook](/api/webhooks) (or you read it from the [History API](/api/server-api)). Browser fingerprinting is one component of Device Intelligence, not the whole thing.

## Example response

A realistic webhook body. The six identifiers arrive alongside the Risk Score and the `signals` that explain it.

```json theme={null}
{
  "request_id": "13f84f05-2f3a-4c2e-9b1f-7a6d3e8c1b22",
  "session_id": "a1c9e740-5b6d-4e2a-8f31-0c2b9d4e7f11",
  "cookie_id": "7e2d1b88-3c4f-4a9e-b6d2-1f8a0c5e9d34",
  "device_id": "5eb7fd5c-9a21-4b6e-8c3d-2f1a9e7b0c45",
  "visitor_id": "161dfbad-4e8c-4d1a-9f72-3b6c0a2e8d57",
  "public_ip": {
    "ip": "203.0.113.42",
    "country": "US"
  },
  "os": "Windows",
  "browser": "Chrome",
  "device_type": "desktop",
  "user_hid": "8f14e45fceea167a5a36dedd4bea2543",
  "risk_score": 30,
    "signals": [
    { "name": "Proxy", "weight": 10 },
    { "name": "Datacenter IP", "weight": 10 },
    { "name": "Abuser Flag", "weight": 10 }
  ],
  "detection_flags": { "proxy": true, "datacenter_ip": true, "abuser": true },
  "observed_at": "2026-06-16T18:00:45Z"
}
```

Here the score is 30 because three independent network signals each added 10: the IP looks like a proxy, it resolves to a datacenter, and it carries a known-abuser reputation. The `signal` strings are display labels and can change — **never branch on them**. Use `detection_flags` to see which signals fired; the same total score can mean different things depending on which flags are set. Read the Risk Score band, the flags, and the action context together — see [Acting on the Risk Score](/guides/acting-on-risk-score).

`device_id` and `visitor_id` are the server-derived identity. `user_hid` is your hashed account id, echoed back. The [API models](/api/models) reference documents every field and its types.

## Next steps

<CardGroup cols={3}>
  <Card title="Anonymity Signals" icon="mask" href="/features/anonymity-signals">
    VPN, proxy, Tor, Privacy Relay, and the mismatch signals around each device.
  </Card>

  <Card title="Risk Scoring" icon="gauge" href="/features/risk-scoring">
    The explainable 0 to 100 score and its bands: Clean, Low, Medium, High.
  </Card>

  <Card title="Accuracy" icon="bullseye" href="/features/accuracy">
    How corroborating independent signals takes identification up to 99%, and where it stops.
  </Card>
</CardGroup>
