> ## 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.

# Quick Start

> Get from zero to your first scored visitor in about five minutes: install the snippet, receive the webhook, and act on the Risk Score.

[Sign up for free](https://app.shieldlabs.ai/) and get 5,000 identifications, or log in if you already have an account. Then follow these steps to your first Risk Score.

<Steps>
  <Step title="Create an account">
    In the [dashboard](https://app.shieldlabs.ai/), create a domain (for example `myshop.com`) — no credit card required. Each domain gets its own key pair:

    | Key                        | Where it lives                        | Purpose                                                                                                  |
    | -------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------- |
    | **Public Key**             | In the browser, in the snippet URL    | Safe to expose. Identifies the domain on the identify call.                                              |
    | **Private API Key**        | Backend only (`sec_…`)                | Authenticates the [History API](/api/server-api) to read scored results.                                 |
    | **Secret Key**             | Backend only, never in the browser    | Authenticates the [Management API](/api/server-api) (profile and balance).                               |
    | **Webhook signing secret** | Backend only (`whsec_…` per endpoint) | Verifies the `X-Shield-Signature` header on incoming webhooks. Copy from the dashboard **Webhooks** tab. |

    <Warning>
      The Secret Key and each webhook `whsec_…` secret stay on the backend, like a database password. See [Keys](/setup/keys) and [Webhooks](/setup/webhooks).
    </Warning>
  </Step>

  <Step title="Install the snippet">
    The snippet is an ES module loaded from the CDN with a dynamic `import()`. It runs in the browser only.

    Drop this into your page and swap in your Public Key.

    ```html theme={null}
    <script type="module">
      const mod = await import(
        'https://cdn.shieldlabs.ai/snippet.js?publicKey=YOUR_PUBLIC_KEY'
      );

      // Anonymous visitor:
      mod.checkAnonymous();

      // Logged-in user (pass a HASHED / pseudonymous id, never a raw email or user id):
      // mod.checkAuthenticatedUser('a1b2c3d4hasheduserid');
    </script>
    ```

    The snippet POSTs the signals automatically and returns a Promise. The browser computes no VisitorID, DeviceID, or Risk Score. Those come back server-side.

    The signature is `checkAnonymous(userHID?, callback?)` and `checkAuthenticatedUser(userHID, callback?)`. The optional callback fires after the POST and gives you the client `ip` and the `requestID`. Use the `requestID` to correlate this check with the webhook you receive — forward it to your backend (shown below) so your server can match the webhook or read the History API for this visit. The first argument is the client IP, not the score. The Risk Score arrives later by webhook.

    ```js theme={null}
    // Anonymous: pass undefined for userHID so the callback lands in the right slot.
    mod.checkAnonymous(undefined, (ip, requestID) => {
      // Forward the requestID to your backend so the server can match the webhook
      // (or read the History API) for this visit. The score never reaches the browser.
      fetch('/api/track-request', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ requestID }),
      });
    });

    // Authenticated:
    // mod.checkAuthenticatedUser('a1b2c3d4hasheduserid', (ip, requestID) => {
    //   fetch('/api/track-request', { method: 'POST',
    //     headers: { 'Content-Type': 'application/json' },
    //     body: JSON.stringify({ requestID }) });
    // });
    ```

    <Note>
      Copy-paste versions for Native JS, React, Angular, Vue, Preact, and Svelte live at [Install the snippet](/setup/snippet).
    </Note>

    <Check>
      **Want to confirm it works right now?** Load a page with the snippet, then open your [dashboard](/features/traffic-analytics). The visit and its Risk Score show up within a few seconds — the fastest check before you wire the score into your own backend (the webhook below, or a read from the [History API](/api/server-api)).
    </Check>
  </Step>

  <Step title="Register and verify the webhook">
    This step wires the score into your own backend. (If you only wanted to see it work, the dashboard check above already did that.)

    Register an endpoint in the dashboard (**Webhooks** tab on your domain). Within about 1 second of the browser check, ShieldLabs POSTs a JSON event to that URL. The signature travels in the `X-Shield-Signature` header, not in the body.

    ```http theme={null}
    POST https://myshop.com/webhooks/shieldlabs
    Content-Type: application/json
    X-Shield-Signature: sha256=3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e

    {
      "event_type": "identification.scored",
      "schema_version": "2026-06-01",
      "created_at": "2026-06-16T18:00:21Z",
      "data": {
        "request_id": "13f84f05-2c4a-4d8e-9b1a-6f2e7c9d0a55",
        "device_id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
        "visitor_id": "161dfbad-e5f6-7890-abcd-ef1234567890",
        "user_hid": "a1b2c3d4hasheduserid",
        "public_ip": { "ip": "203.0.113.42", "country": "US" },
        "connection_type": "browser_vpn_proxy",
        "risk_score": 40,
        "signals": [
          { "name": "browser_vpn_proxy", "weight": 30 },
          { "name": "timezone_mismatch", "weight": 10 }
        ],
        "observed_at": "2026-06-16T18:00:21Z"
      }
    }
    ```

    <Note>
      Each signal `name` is a stable slug — branch on `name`, `detection_flags`, and `risk_score`, not on display labels from the History API.
    </Note>

    Always verify `X-Shield-Signature` before trusting the payload. It is `sha256=` plus the hex HMAC-SHA256 of the **raw request body**, keyed with that endpoint's `whsec_…` signing secret.

    ```js Node.js theme={null}
    import crypto from 'crypto';
    import express from 'express';

    const SECRET = process.env.SHIELDLABS_WEBHOOK_SECRET; // whsec_… for this endpoint
    const app = express();

    app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf; } }));

    app.post('/webhooks/shieldlabs', (req, res) => {
      const received = req.get('X-Shield-Signature') ?? '';
      const expected =
        'sha256=' +
        crypto.createHmac('sha256', SECRET).update(req.rawBody).digest('hex');

      const a = Buffer.from(expected);
      const b = Buffer.from(received);
      if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
        return res.sendStatus(401);
      }

      res.sendStatus(200);
      handleScore(req.body); // idempotent on request_id
    });
    ```

    <Warning>
      HMAC the raw request body bytes as received. Re-serializing the parsed JSON changes the bytes and the signature will not match.
    </Warning>

    Two behaviors to handle up front:

    * **One webhook per scored identification.** The server waits up to \~60s for optional follow-up network checks, then sends the final `score` once. When no follow-up is expected, it arrives in about a second.
    * **There are no retries.** Delivery is at-most-once. Make your handler idempotent on `request_id`, and poll the [History API](/api/server-api) for guaranteed reads.

    More detail lives in [Webhooks](/setup/webhooks).
  </Step>

  <Step title="Act on the Risk Score">
    The Risk Score is 0 to 100, capped at 100. Higher means more anonymous, more likely masked, spoofed, or abusive. Every score ships with `signals`, so you see which signals fired.

    Decide what to do per band. These are recommendations, not enforced rules. Your code owns the verdict.

    | Band       | Range  | Meaning                                    | What your code might do                   |
    | ---------- | ------ | ------------------------------------------ | ----------------------------------------- |
    | **Clean**  | 0–9    | No meaningful signals                      | Pass through, no friction                 |
    | **Low**    | 10–29  | One minor signal                           | Allow, worth logging                      |
    | **Medium** | 30–59  | Several overlapping or one moderate signal | Step-up challenge, second look, or review |
    | **High**   | 60–100 | Strong anonymity signals                   | Block, review, or require verification    |

    ```js theme={null}
    async function handleScore(payload) {
      const { request_id, score, signals, user_hid } = payload;

      // Idempotent: a redelivery may carry the same request_id.
      await db.checks.upsert({ requestID: request_id, score, signals });

      if (score >= 60) {
        await requireStepUp(user_hid);        // High: verify before a sensitive action
      } else if (score >= 30) {
        await flagForReview(user_hid, score); // Medium: a second look
      }
      // Clean / Low: allow.
    }
    ```

    <Warning>
      A legitimate user can score high (corporate proxy, VPN, or a privacy browser). Decide on **Score + signals + action context**, never the number alone. Tune thresholds gradually.
    </Warning>
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Identification" icon="fingerprint" href="/features/identification">
    Persistent VisitorID and DeviceID, and how they survive cleared cookies.
  </Card>

  <Card title="Anonymity Signals" icon="user-secret" href="/features/anonymity-signals">
    VPN, proxy, Tor, and anti-detect browser signals, with the scoring reference.
  </Card>

  <Card title="Risk Scoring" icon="gauge" href="/features/risk-scoring">
    How the 0 to 100 score is built, what `signals` contains, and the bands.
  </Card>

  <Card title="Patterns" icon="diagram-project" href="/features/patterns">
    Ready-made patterns computed across your historical traffic. They refresh periodically, so newly seen entities surface after a short delay, not instantly.
  </Card>

  <Card title="Traffic Analytics" icon="chart-line" href="/features/traffic-analytics">
    Rank every source by the risk and anonymous-traffic share it delivers.
  </Card>

  <Card title="Acting on the Risk Score" icon="code-branch" href="/guides/acting-on-risk-score">
    Turn the score and signals into allow, challenge, review, and block logic.
  </Card>
</CardGroup>
