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

# Security

> Protect your ShieldLabs keys, webhooks and data in transit.

Securing a ShieldLabs integration comes down to four things: verify that every webhook really came from us, keep your server-side keys on the server, run everything over HTTPS, and know what protects the snippet's payload. This page is the reference for each.

<Note>
  These are integration security controls. ShieldLabs returns a Risk Score (`0-100`: Trusted / Suspicious / Dangerous) and its named risk signals on each identification; you choose the action for each case and act on the result in your backend.
</Note>

## Webhook authenticity (HMAC-SHA256)

Your webhook URL is a public endpoint. Anyone who learns it can POST to it. The only thing that proves a delivery actually came from ShieldLabs is its signature, so **verify every webhook before you trust the body.** The signature is the `X-Shield-Signature` header: `sha256=` plus the hex HMAC-SHA256 (a keyed cryptographic hash that only someone holding the secret can produce) of the **raw request body**, keyed with that endpoint's `whsec_…` signing secret, constant-time compared, rejecting with `401` on a mismatch.

The exact formula, the Node, Go, and Python handlers, the raw-bytes gotcha, and idempotency on `request_id` all live on the [webhooks](/setup/webhooks) page.

## Key handling

Every domain has a **Public Key**, a **Private API Key**, and a **Secret Key**, scoped to that single domain. They have different trust levels. The [API keys](/setup/keys) page is the full reference for which key each API uses.

| Key | Where it belongs | What it can do | Safe in the browser? |
| - | - | - | - |
| **Public Key** | The snippet URL on your site | Identifies the domain, so each identification is counted to the right site and account | Yes, by design |
| **Private API Key** | Your server only (`sec_…`) | Authenticates the [History API](/api/server-api) on `account.shieldlabs.ai` (`Authorization: Bearer`) | No, never |
| **Secret Key** | Your server only | Authenticates the [Management API](/api/server-api) on `api.shieldlabs.ai` (`Authorization: Bearer` + `X-Shield-Domain`) | No, never |
| **Webhook signing secret** | Your server only (`whsec_…` per endpoint) | Verifies the `X-Shield-Signature` header on incoming webhooks | No, never |

<Frame caption="Integration > API keys in the analytics dashboard: one domain's Private API Key, masked, with tabs for Public Key, Private API Key and Secret Key.">
  <img className="block dark:hidden" src="https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys.png?fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=6e6aee6727e4401e869acc2b46b77cf1" alt="Integration > API keys for example.com in the analytics dashboard: tabs Public Key, Private API Key (selected) and Secret Key; the Private API Key masked after its first characters, sec_6eo9l8 followed by dots; Active, last used 14m ago, 7d usage 11,020, a copy button and the Rotate icon." data-og-width="2270" width="2270" data-og-height="510" height="510" data-path="images/dashboard/integration-keys.png" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys.png?w=280&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=f46efe049c56b091485c3002f2286ab7 280w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys.png?w=560&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=2553269a78bed9ddea6b7af4425fe2a0 560w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys.png?w=840&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=c4a73f982e465bdf3c07a1c89dadafd0 840w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys.png?w=1100&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=07ee78932be5d9ee820bcb2b124c4763 1100w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys.png?w=1650&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=d9686fc7e533e85bf95be31be326056a 1650w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys.png?w=2500&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=fd994ea808c37b7206e30a31e8efc51c 2500w" />

  <img className="hidden dark:block" src="https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys-dark.png?fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=9ec59e34adea359b69d37b0e0fd0c65b" alt="Integration > API keys for example.com in the analytics dashboard in the dark theme: tabs Public Key, Private API Key (selected) and Secret Key; the Private API Key masked after its first characters, sec_6eo9l8 followed by dots; Active, last used 14m ago, 7d usage 11,020, a copy button and the Rotate icon." data-og-width="2270" width="2270" data-og-height="510" height="510" data-path="images/dashboard/integration-keys-dark.png" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys-dark.png?w=280&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=8701e61e0c76f999ba8d48c293450057 280w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys-dark.png?w=560&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=266678df15ba4019daadeb35bdf3c524 560w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys-dark.png?w=840&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=ef1df805efa01a26d26b0dd45795d8fb 840w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys-dark.png?w=1100&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=7020e5eec0287fe156c7b86ea5db2cba 1100w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys-dark.png?w=1650&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=3d758e7b6267caeba55c0e801feb2981 1650w, https://mintcdn.com/shieldlabs-725d18f1/JyleDzUFYU3SXP4Q/images/dashboard/integration-keys-dark.png?w=2500&fit=max&auto=format&n=JyleDzUFYU3SXP4Q&q=85&s=55c5af806f9252db7cff75ffeda72dd6 2500w" />
</Frame>

The Public Key is meant to be visible. It ships in your page source as the `?publicKey=` parameter and cannot read data, change settings, or authenticate against the Server API. A browser request is accepted only when the Public Key matches the domain the page is served from (anything else gets `401` before it counts), so a key lifted from your page does not work on someone else's site. Scripted traffic that presents your domain typically shows up with automation risk signals, and you can rotate the key.

Your server-side keys (the Private API Key and the Secret Key) authenticate the Server API. **Anyone holding one can read your domain's data**, so they must never reach the browser. Webhook endpoints use separate `whsec_…` secrets; treat a leaked webhook secret the same way and use **Rotate secret** on the endpoint under **Integration > Webhooks** in the analytics dashboard.

<Warning>
  Never put the Secret Key in client-side code, the snippet, a public repository, a build artifact, or any place a browser can reach. Store it in an environment variable or a secrets manager. Use a different key set for every domain so a leak is contained to one site.
</Warning>

### Rotate when exposed

If a secret may have leaked (a committed `.env`, a log line, an offboarded teammate), open **Integration > API keys** in the analytics dashboard and **Rotate** the exposed key right away. **Rotate** replaces only the key you select, and the previous value stops working immediately, so update the snippet (Public Key) or your server-side code (Private API Key or Secret Key) in the same change. The rotation flow and the profile health check live on the [API keys](/setup/keys) page, and [Integration](/dashboard/integration) describes the screen.

## Transport security (HTTPS / TLS)

Every ShieldLabs web endpoint is served over HTTPS.

* **ShieldLabs hosts** (`cdn.shieldlabs.ai`, `rest.shieldlabs.ai`, `webrtc.shieldlabs.ai`, `api.shieldlabs.ai`, `account.shieldlabs.ai`, `app.shieldlabs.ai`) are served over TLS (the encryption behind HTTPS). The snippet also connects to `wss://rest.shieldlabs.ai` and, for its network check, to `ice.shieldlabs.ai`; the [CSP](/setup/csp) page lists the exact directives.
* **Your webhook callback URL must be HTTPS.** It receives signed Risk Scores and identifiers, so terminate TLS in front of your handler.
* **Your Server API calls must be HTTPS.** They carry your server-side keys, so a plaintext request would put a credential on the wire. Always call over HTTPS (`https://account.shieldlabs.ai/…`, `https://api.shieldlabs.ai/…`).

<Warning>
  The snippet requires a **secure context** (in practice, an HTTPS page, or `localhost` for local development). On an insecure `http://` page the snippet cannot run as intended. Serve any page that loads the snippet over HTTPS.
</Warning>

## Payload protection

The snippet posts to `rest.shieldlabs.ai` over HTTPS, and TLS keeps the payload confidential in transit. Each payload is also sealed with AES-256-GCM and bound to its request ID and a one-time server challenge, and a request ID that was already processed is refused (`409`).

There is nothing for you to configure. Keep every page that loads the snippet on HTTPS.

## Data handling on your side

A few practices keep the data you exchange with ShieldLabs clean.

* **Pass a hashed User HID, never a raw identifier.** Call `checkAuthenticatedUser` with a hashed or pseudonymous account id, not a real email or user id. The User HID is the account key: users, account-level risk and all four High-Risk Events are built on it. It comes back as `user_hid` in webhooks and History rows, so keep it opaque.
* **The Public Key is the only credential in the browser.** Identifiers like the Cookie ID and Session ID live client-side by design and break on storage clear. The durable Device ID and the Risk Score are derived server-side and reach you through verified webhooks and the [Server API](/api/server-api); the page never assembles them.
* **Keep raw signals server-side.** Read Risk Scores and risk signals from your verified webhook handler or the History API, and apply the allow / step up / review / block action in your backend.

The [privacy](/privacy) page covers what is and is not collected, and who controls retention.

## Responsible disclosure

If you find a security issue in ShieldLabs, report it privately to **[contact@shieldlabs.ai](mailto:contact@shieldlabs.ai)**. Please include enough detail to reproduce it, and give us a reasonable window to confirm and fix before any public disclosure. We do not pursue good-faith researchers who follow coordinated disclosure.

## Security checklist

<Steps>
  <Step title="Verify every webhook">
    Constant-time compare `X-Shield-Signature` against HMAC-SHA256 of the raw request body, keyed with the endpoint's `whsec_…` secret. Reject with `401` on a mismatch.
  </Step>

  <Step title="Keep the secret on the server">
    Secret Key in an environment variable or secrets manager, never in the browser. One key set per domain.
  </Step>

  <Step title="Rotate on exposure">
    Rotate the exposed key the moment it may have leaked, then update the snippet and your server in the same change.
  </Step>

  <Step title="HTTPS everywhere">
    Snippet pages, your callback URL, and your Server API calls all over TLS. The snippet needs a secure context to run.
  </Step>

  <Step title="Hash the User HID">
    Send only a hashed or pseudonymous account id to `checkAuthenticatedUser`.
  </Step>
</Steps>

## Related pages

<CardGroup cols={2}>
  <Card title="Webhooks" icon="webhook" href="/setup/webhooks">
    `X-Shield-Signature` verification in Node, Go, and Python, and idempotency on `request_id`.
  </Card>

  <Card title="API keys" icon="key" href="/setup/keys">
    The Public Key, Private API Key and Secret Key, which API each one opens, and rotation.
  </Card>

  <Card title="Content Security Policy" icon="shield-halved" href="/setup/csp">
    The exact `script-src` and `connect-src` directives the snippet needs.
  </Card>

  <Card title="Privacy" icon="user-shield" href="/privacy">
    What is and is not collected, and who controls retention.
  </Card>
</CardGroup>


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