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

# Domains

> Add and manage the domains ShieldLabs identifies visitors on, and find each domain's keys, webhooks, and usage.

A **domain** is the unit of integration in ShieldLabs. You add each site you want to identify visitors on as its own domain, and that domain gets its own key set, its own webhook endpoints (up to 10), and its own request totals. Nothing is shared across domains: a key set issued for one domain does not work on another.

If you run a single site, you have one domain. If you run several sites (or staging and production), each is a separate domain with its own configuration.

## Add a domain

Domains are created in the dashboard, on the **Integration** tab.

<Steps>
  <Step title="Open the Integration tab">
    Go to [app.shieldlabs.ai](https://app.shieldlabs.ai/) and open the **Integration** tab.
  </Step>

  <Step title="Add the domain">
    Enter the hostname you want to identify visitors on, for example `myshop.com`. Adding it provisions the domain's **public key**, **private API key**, and **secret key**.
  </Step>

  <Step title="Install the snippet">
    Drop the snippet onto that domain with its public key in the URL, following [Install the snippet](/setup/snippet) for the full client setup.

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

  <Step title="Register webhook endpoints">
    Add one or more endpoints on the domain's **Webhooks** tab so scores are delivered to your server. See [Webhooks](/setup/webhooks) for registration, verification, and testing.
  </Step>
</Steps>

<Note>
  Adding a domain mints a fresh key set on the spot. The [secret key](/setup/keys) is shown in full only at that moment. Capture it then and store it server-side.
</Note>

## What every domain carries

| Field               | What it is                                                                                                                                                                   |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **PublicKey**       | Goes in the snippet URL as `?publicKey=`, safe to expose in the browser. Identifies which domain a fingerprint belongs to. See [keys](/setup/keys) for the format.           |
| **Private API Key** | Server-side only (`sec_…`). Authenticates the [History API](/api/server-api) on `account.shieldlabs.ai` (`Authorization: Bearer`).                                           |
| **Secret**          | Server-side only. Authenticates the Management API on `api.shieldlabs.ai` (`Authorization: Bearer` + `X-Shield-Domain`). See [keys](/setup/keys).                            |
| **Webhooks**        | Up to 10 HTTPS endpoints per domain, each with its own `whsec_…` signing secret. Managed on the **Webhooks** tab.                                                            |
| **Enabled**         | Whether the domain is active. A disabled domain rejects identify calls and Server API auth with `401`.                                                                       |
| **Weight**          | The domain's remaining request balance, returned by the Profile endpoint. Across multiple domains, each carries its own balance. Billing is per request, counted per domain. |

You can read the live configuration for a domain at any time with the [Profile endpoint](/api/server-api). It returns both keys masked (see [keys](/setup/keys)), so you can confirm a domain without exposing its credentials:

```bash theme={null}
curl "https://api.shieldlabs.ai/v1/profile" \
  -H "X-Shield-Domain: myshop.com" \
  -H "Authorization: Bearer YOUR_SECRET_KEY"
```

```json theme={null}
{
  "Domain": "myshop.com",
  "Weight": 148230,
  "PublicKey": "****************************8c7d",
  "Secret": "****************************3847",
  "CreatedAt": "2025-11-21T18:00:21Z"
}
```

<Tip>
  The Profile call is free: it does not consume any of the domain's requests. Use it as a quick health check that a domain is enabled.
</Tip>

## Verification is automatic

You do not add a DNS record or upload a file to verify a domain. Verification happens on its own once live snippet traffic is seen.

<Steps>
  <Step title="Install the snippet with the domain's public key">
    The public key only works on the domain it was issued for. The server resolves the domain from the request `Origin`, then `Referer`, then `Host`, and checks it against the public key.
  </Step>

  <Step title="Trigger one identify call">
    Load a page that runs the snippet. The first fingerprint that arrives for that domain marks it as verified in the dashboard.
  </Step>

  <Step title="Confirm in the dashboard">
    The domain flips to verified on the **Integration** tab once that first call is recorded.
  </Step>
</Steps>

<Note>
  If a public key is served from a host it was not issued for, the identify call is rejected with `401`, and the domain stays unverified. A key lifted from your page source will not work on someone else's site.
</Note>

## Subdomains and host matching

A key set is scoped to the exact host you registered. The server resolves the domain from the request `Origin`, then `Referer`, then `Host`, and looks it up against the registered domains. The match is exact, with one normalization: a leading `www.` is stripped, so `www.myshop.com` and `myshop.com` resolve to the same domain.

Subdomains do not inherit a parent domain's key set. `app.myshop.com` and `checkout.myshop.com` are distinct hosts: each one you want to identify visitors on needs its own domain entry, with its own public key, secret key, and request totals.

<Tip>
  Register each subdomain you serve the snippet on as its own domain. That keeps every host on its own key set and its own request balance, and lets you point each one at different webhook endpoints if you need to.
</Tip>

## Per-domain isolation

Every domain is a self-contained unit.

<CardGroup cols={2}>
  <Card title="Separate credentials" icon="key">
    Each domain has its own public key and secret key. A key set issued for one domain authenticates only that domain. Rotating one domain's keys never touches another's.
  </Card>

  <Card title="Separate webhooks" icon="webhook">
    Each domain can register up to 10 webhook endpoints. Point them at the same handler or different handlers, as you prefer.
  </Card>

  <Card title="Separate request totals" icon="chart-simple">
    With multiple domains you can see how the request balance splits across them.
  </Card>

  <Card title="Independent enable state" icon="toggle-on">
    Disabling one domain stops its identify calls and Server API access without affecting the others.
  </Card>
</CardGroup>

## Next steps

With the domain added, wire its public key into the [snippet](/setup/snippet), keep its secret key on your server, and register webhook endpoints that verify `X-Shield-Signature` per the [webhooks](/setup/webhooks) guide.
