> ## 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 the sites you protect as domains and manage each one's keys, webhook endpoints and status.

A **domain** is a site you protect with ShieldLabs. Your account holds one or more domains. Each domain gets its own key set and its own webhook endpoints (up to 10), and all of them draw on your account's included identifications. A key set issued for one domain works only on that domain, and on its subdomains while the domain accepts them.

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

Add domains in the analytics dashboard under **Integration > Domains**. [Integration](/dashboard/integration) describes the screen.

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

  <Step title="Add the domain">
    Enter the hostname you want to protect, for example `myshop.com`, without `https://` or a path. Subdomains such as `app.myshop.com` are accepted by default; you can switch that off for the domain. 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. On pages where users are signed in, call `checkAuthenticatedUser` with a hashed User HID instead, as [Identify signed-in users](/setup/snippet#identify-signed-in-users) shows.

    ```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 webhook endpoints for the domain under **Integration > Webhooks**, so each identification's Risk Score reaches 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. Store the [Secret Key](/setup/keys) and the Private API Key server-side, in environment variables or a secrets manager.
</Note>

## What every domain carries

| Field | What it is |
| - | - |
| **Public Key** | Goes in the snippet URL as `?publicKey=`, safe to expose in the browser. Identifies which domain an identification belongs to. See [API 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 Key** | Server-side only. Authenticates the Management API on `api.shieldlabs.ai` (`Authorization: Bearer` + `X-Shield-Domain`). See [API keys](/setup/keys). |
| **Webhooks** | Up to 10 HTTPS endpoints per domain, each with its own `whsec_…` signing secret. Managed under **Integration > Webhooks** in the analytics dashboard. |
| **Subdomains** | Whether calls from subdomains are accepted and reported under this domain: **Accepted** (the default) or **Exact host**. See [Subdomains and host matching](#subdomains-and-host-matching). |
| **Status** | **Reporting** (identifications are arriving), **Pending** (no identification yet), **Paused** (you paused it) or **Frozen** (at the rate limit, resumes on its own). A paused domain rejects identification calls and Server API auth with `401`; its keys, subdomain setting and webhook endpoints stay as they are. |
| **Remaining volume** (`Weight`) | The remaining included volume on your account, returned by the Profile endpoint. All domains on the account share one plan quota, and each identification counts once. |

<Frame caption="Integration > Domains in the analytics dashboard: each domain's status and subdomain setting.">
  <img className="block dark:hidden" src="https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains.png?fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=364f64c6ddc59dcfe0d688f23fd64146" alt="Integration > Domains in the analytics dashboard: example.com Reporting with 11,020 identifications in the last 7 days and subdomains Accepted, dev.example.com Reporting with 1,460 and Exact host, shop.example.com Paused with 0 and Exact host, and Upgrade plan to add a domain." data-og-width="2270" width="2270" data-og-height="736" height="736" data-path="images/dashboard/integration-domains.png" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains.png?w=280&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=9230188ade90ff7b34d9bdb6593a3c07 280w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains.png?w=560&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=901aaaa19de3e47a8ec4aa4b60625442 560w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains.png?w=840&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=735f3ed75c31d449fe1233c3e7ef18ca 840w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains.png?w=1100&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=fa1f9f09eaf415e62e45424466b0c069 1100w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains.png?w=1650&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=968e30cfd2c176ef386cd11b5600f819 1650w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains.png?w=2500&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=705f87135cdf59888d1318cf32383e58 2500w" />

  <img className="hidden dark:block" src="https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains-dark.png?fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=30f8e9ae896a0e5642fa61f010a31bee" alt="Integration > Domains in the analytics dashboard in the dark theme: example.com Reporting with 11,020 identifications in the last 7 days and subdomains Accepted, dev.example.com Reporting with 1,460 and Exact host, shop.example.com Paused with 0 and Exact host, and Upgrade plan to add a domain." data-og-width="2270" width="2270" data-og-height="736" height="736" data-path="images/dashboard/integration-domains-dark.png" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains-dark.png?w=280&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=6aa7eb924aad9e16bfe086d5069024de 280w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains-dark.png?w=560&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=ee4a4695cf12d0e53de05afaab36fbfd 560w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains-dark.png?w=840&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=0b118b78e48b78546a5820128128dcd9 840w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains-dark.png?w=1100&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=a640bac860e80295e08d18d56ba4d17a 1100w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains-dark.png?w=1650&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=d14752ac212904313e5df051d9aa0eb8 1650w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-domains-dark.png?w=2500&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=dd1eb1afe477e2eff92a4f895f3ff9f3 2500w" />
</Frame>

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 [API 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,
  "Callback": "",
  "PublicKey": "****************************8c7d",
  "Secret": "****************************3847",
  "CreatedAt": "2025-11-21T18:00:21Z"
}
```

<Tip>
  The Profile call is free: it never counts against your plan. 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 works on the domain it was issued for, and on its subdomains while the domain accepts them. ShieldLabs resolves the domain from the request `Origin`, then `Referer`, then `Host`, and checks it against the Public Key.
  </Step>

  <Step title="Trigger one identification">
    Load a page that runs the snippet. The first identification that arrives for that domain verifies it.
  </Step>

  <Step title="Confirm in the analytics dashboard">
    Under **Integration > Domains**, the domain's status changes from **Pending** to **Reporting** once that first identification is recorded.
  </Step>
</Steps>

<Note>
  If a Public Key is served from a host it was not issued for, the identification call is rejected with `401`, and the domain stays unverified. A key lifted from your page source works only on the domain it was issued for and, while that domain accepts them, its subdomains.
</Note>

## Subdomains and host matching

ShieldLabs resolves the host of each call from the request `Origin`, then `Referer`, then `Host`, and strips a leading `www.`, so `www.myshop.com` and `myshop.com` are the same domain. A registered host that matches exactly always wins. Otherwise, while a domain accepts subdomains (the default for every new domain), calls from its subdomains, such as `app.myshop.com` or `checkout.myshop.com`, are accepted with that domain's Public Key and reported under it.

<Tip>
  To keep a subdomain apart, with its own key set, webhook endpoints and figures in the analytics dashboard, add it as its own domain: the exact match takes precedence over the parent. Switch off subdomain traffic on the parent when only the exact host should be accepted. Each added domain uses one of your plan's domain slots.
</Tip>

## How many domains you can add

Your plan sets how many active domains one account can hold.

| Plan | Active domains |
| - | :-: |
| Free | 1 |
| Starter | 1 |
| Growth | 3 |
| Scale | 5 |

Adding one past the cap returns an error naming the plan and its limit, for example `starter plan allows at most 1 domain`. Deleting a domain frees its slot, and moving to a higher plan raises the cap right away. If you are already above the cap after a plan change, the domains you have keep running; only new ones are refused.

Each active domain also shares one ingest budget across every visitor IP on that host (Free and Starter **5** requests/second, Growth **10**, Scale **15**). Crossing it returns `429` without banning the domain. After ten saturated seconds in a row the domain shows as **Frozen** in the analytics dashboard: calls get `429`, are not billed, and processing resumes on its own. The full gateway table is on [Rate limits](/rate-limits).

## What is per domain and what is shared

Each domain has its own credentials, webhooks and status. Your account's included identifications are shared by all of them.

<CardGroup cols={2}>
  <Card title="Separate credentials" icon="key">
    Each domain has its own Public Key, Private API 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="One shared quota" icon="chart-simple">
    All domains draw on your account's included identifications, tracked for the billing cycle on the [Usage](/dashboard/usage) screen. Pick **All domains** or one domain in the analytics dashboard to see its identifications.
  </Card>

  <Card title="Independent status" icon="toggle-on">
    Pausing one domain stops its identification 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) and identify signed-in users with `checkAuthenticatedUser`, keep its Secret Key and Private API Key on your server, and register webhook endpoints that verify `X-Shield-Signature` per the [webhooks](/setup/webhooks) guide.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.