# Add ShieldLabs to your website or app

Set up identification, risk signals and Risk Score for your website or application using the SDKs. Connect through OAuth MCP by default, or use the dashboard fallback.

For apps with signed-in users, also offer User HID linking to connect identifications with the app's user accounts and enable account-level history and High-Risk Events.

## Before you start

Never print `.env` files, API keys or OAuth codes. Private keys stay on the server. Store one-time credentials privately; MCP clients may record responses.

Guide the user through major stages. Ask for missing choices, then do the selected stage without asking before every file or package. Reuse choices and approvals already given; if the user selected several stages, complete them without asking again. Keep user-facing updates in plain language: "Connecting ShieldLabs", "Waiting for the first result" or "Verifying the integration", in the user's language. Handle expected asynchronous waits and recoverable failures internally within the bounded verification steps below. Do not show routine not_found responses, HTTP codes, stack traces, raw diagnostics or speculative billing warnings as installation errors. Keep necessary diagnostic evidence in the local setup report without secrets. If progress requires the user, explain the current unfinished step briefly and ask only for the specific action needed. Confirm success only after verification; a quiet setup must never mean claiming that an unverified integration works.

Before editing, show a short progress map: account/MCP, domain coverage, browser SDK, stored
result, and selected server/user-linking/HRE/webhook stages. Mark each verified (with evidence),
needs check, pending, not selected or blocked. On resume inspect existing code and read current
state; reuse verified work and do not repeat mutations or questions already settled.

## Step 1: Check for a website or app

Inspect the existing website or application: project manifests, HTML pages, shared scripts and build configuration. Keep an existing static website on its current stack; no package manifest does not mean the project is empty. Only when no website or app exists, ask what to create, suggest Next.js if the user has no preference, then continue.

## Step 2: Check for an existing integration

Look for `@shieldlabs-ai/` dependencies, a ShieldLabs snippet or identification code. If found, explain what exists and ask before changing it.

## Step 3: Choose one setup path

Use **Path A** by default. Use **Path B** if the user prefers the dashboard or their client does not support OAuth MCP. Follow one path, then continue to Step 4. Pending OAuth approval or missing tools after sign-in means pause and finish connecting; do not silently switch paths.

### Path A: OAuth MCP

Reuse a working connection or connect `https://mcp.shieldlabs.ai/mcp` through OAuth. A saved server entry alone does not prove authentication or tool availability. Configure the coding client running this task, regardless of its model provider. Follow only that client's row below. Merge into existing configuration and reuse an existing `shieldlabs` entry; preserve other servers.

| Coding client | Add ShieldLabs and sign in |
| --- | --- |
| Codex | Add with `codex mcp add shieldlabs --url https://mcp.shieldlabs.ai/mcp`. If adding starts OAuth, finish that flow; otherwise run `codex mcp login shieldlabs`. |
| Claude Code | Run `claude mcp add --transport http shieldlabs https://mcp.shieldlabs.ai/mcp`, then authenticate through `/mcp`. |
| Cursor | Merge the JSON below into `.cursor/mcp.json`. Connect and sign in from Cursor's MCP settings. |
| Kimi Code CLI | Merge the JSON below into `.kimi-code/mcp.json`. Start a new Kimi Code session, then run `/mcp-config login shieldlabs` inside it. |
| VS Code / GitHub Copilot | Run `MCP: Add Server` in the Command Palette, choose HTTP, enter `https://mcp.shieldlabs.ai/mcp`, name it `shieldlabs`, select the configuration destination and follow sign-in. |
| Gemini CLI | Run `gemini mcp add --transport http shieldlabs https://mcp.shieldlabs.ai/mcp`, then `/mcp auth shieldlabs` inside Gemini CLI. |
| Cline | Open MCP Servers → Remote Servers. Add `shieldlabs` with `https://mcp.shieldlabs.ai/mcp` and Streamable HTTP transport, then complete OAuth. In Cline CLI, use the `cline mcp` setup and OAuth menu. |
| OpenCode | In `opencode.json`, merge `{"mcp":{"shieldlabs":{"type":"remote","url":"https://mcp.shieldlabs.ai/mcp"}}}`, then run `opencode mcp auth shieldlabs`. |
| Windsurf / Devin Desktop (Cascade) | Open the Cascade actions menu → Open MCP config file. Merge the JSON below into its MCP configuration, then connect and complete OAuth. |

For the Cursor, Kimi Code or Cascade row, merge this configuration into the indicated file:

```json
{
  "mcpServers": {
    "shieldlabs": { "url": "https://mcp.shieldlabs.ai/mcp" }
  }
}
```

For another coding client, use its documented remote HTTP MCP and OAuth setup with the same endpoint. If the client cannot connect through OAuth MCP, choose Path B. Do not put a domain API key, copied bearer token or client secret in this MCP configuration.

**Open ShieldLabs in the browser.** Start one OAuth flow through the detected client's sign-in control or command. Explain what the user will see:

- Already signed in to ShieldLabs in that browser: review and approve MCP access.
- Existing account, signed out: sign in, then review and approve MCP access.
- New account: choose registration on the ShieldLabs page, finish creating the account, then review and approve MCP access.

Keep the flow in the same browser so it can return to the pending approval. Open the original authorization URL in that browser; do not copy an intermediate login, registration or consent URL to another browser. Do not ask separately whether the user has an account. If the browser does not open, give the fresh authorization URL as a direct clickable link. For Codex, use `--no-browser` only when the user requests a manual link and the installed CLI supports it. Keep one active login flow; do not run a second login while the first is waiting. If the request expires during registration, start one fresh OAuth flow; reuse the account just created. Wait for the client's successful authentication result.

**Continue in the current chat.** Check whether ShieldLabs tools are callable. If they are, continue immediately. If they are missing, save a short checkpoint to `.shieldlabs/setup-state.md` in the project before pausing: the guide URL, detected client, chosen path, selected stages and domain if known, verified steps, unfinished checks and next action. Update an existing checkpoint without overwriting unrelated content. Store no keys, tokens, OAuth URLs/codes or secret values; retain only names of configuration files or environment variables. On resume, read the checkpoint and recheck live state before continuing.

If tools are still missing after OAuth, tell the user to restart the detected coding client by its name, for example "Restart Codex" or "Restart Claude Code", then reopen this chat. Do not assume an operating system, keyboard shortcut or a per-server Restart button. Do not restart the user's client yourself. Ask the user to return to this chat and send:

> Continue the ShieldLabs setup from the saved checkpoint. Check MCP tool availability, domain coverage and the configured key, then continue the integration.

Give that message in the user's language. If tools remain unavailable in the resumed chat, ask them to open a new chat in the same project and send the same message. Reuse completed work; do not repeat registration, domain creation or key rotation. **Pause here until tools are callable. Do not install SDKs, change integration code or publish while this connection step is pending.**

Run `shieldlabs_check_connection` and `shieldlabs_list_domains`. Select an enabled domain and keep its public UUID (`id`) for History reads; the hostname is used to check website coverage. Reuse the user's domain and configured keys. For a new authorized domain, call `shieldlabs_add_domain`. Use `include_secret=true` only when the backend needs its one-time private key, and save it privately. Inventory production/staging/preview hostnames and inspect parent `allow_subdomains`. Covered child hostnames can reuse the parent keys; separate keys are an isolation choice, not a reason to register every subdomain.

Existing server keys are masked. If unavailable locally, ask for secure configuration; rotate through MCP only with explicit approval.

Before Step 4, require successful `shieldlabs_check_connection` and `shieldlabs_list_domains` calls, confirm coverage of the intended hostname, and verify that the configured public key belongs to the selected site. Check any private key required for the selected backend stage through secure local configuration. Never treat OAuth success alone as a completed setup or substitute placeholder keys.

### Path B: ShieldLabs dashboard

Guide the user to https://app.shieldlabs.ai to sign in or register, add or select their domain and configure its keys locally. Reuse existing configuration. Never ask for private keys in chat or rotate them automatically. Confirm domain coverage and the required keys before continuing to Step 4 without MCP.

## Step 4: Install the selected stage

Start only after the chosen path's connection, domain coverage and required key checks above are complete. If a check is pending, save the checkpoint and pause before installing packages or changing SDK integration code.

State the detected stack, domain, action and proposed changes. If that scope has not been approved, ask once before editing; an explicit installation request already authorizes its necessary changes.

For a general installation request, begin with browser identification. Add server verification now only if the user requested it or selected it. Use the project's existing framework and package manager; select its SDK below instead of adding a different framework.

Use `@shieldlabs-ai/js@1.1.0` for browser identification. Add only the matching framework package when needed:

| Browser stack | SDK and integration guide |
| --- | --- |
| JavaScript / TypeScript without a framework | `@shieldlabs-ai/js`; [JavaScript guide](https://docs.shieldlabs.ai/sdks/javascript). |
| React | `@shieldlabs-ai/react`; [React guide](https://docs.shieldlabs.ai/sdks/react). |
| Next.js | `@shieldlabs-ai/react` for browser identification; `@shieldlabs-ai/node@1.3.2` directly in server routes when server verification is selected; [Next.js guide](https://docs.shieldlabs.ai/sdks/nextjs). |
| Vue | `@shieldlabs-ai/vue`; [Vue guide](https://docs.shieldlabs.ai/sdks/vue). |
| Svelte / SvelteKit | `@shieldlabs-ai/svelte`; [Svelte guide](https://docs.shieldlabs.ai/sdks/svelte). |
| Angular | `@shieldlabs-ai/angular`; [Angular guide](https://docs.shieldlabs.ai/sdks/angular). |

For selected server verification, use the backend's language, independently of its frontend:

| Backend | SDK and integration guide |
| --- | --- |
| Node.js / TypeScript | `@shieldlabs-ai/node@1.3.2`; [Node.js guide](https://docs.shieldlabs.ai/sdks/node). |
| Python | `shieldlabs`; [Python guide](https://docs.shieldlabs.ai/sdks/python). |
| Go | `github.com/ShieldLabs-ai/shieldlabs-go`; [Go guide](https://docs.shieldlabs.ai/sdks/go). |
| PHP | `shieldlabs/shieldlabs-php`; [PHP guide](https://docs.shieldlabs.ai/sdks/php). |
| Java | `ai.shieldlabs:shieldlabs-java`; [Java guide](https://docs.shieldlabs.ai/sdks/java). |
| .NET | `ShieldLabs`; [.NET guide](https://docs.shieldlabs.ai/sdks/dotnet). |

Check published versions and the installed SDK's exported API/types before writing integration code. Configure the public key in the frontend and any needed private key on the server. Choose the call from the actual SDK contract and the selected use case:

| Use case | Browser SDK call |
| --- | --- |
| Passive collection on a website or page without a protected action | Load once and use `agent.check()`. Framework wrappers may offer `checkOnLoad`; use it only if that package exports it. |
| A fresh check for an existing signup, login, checkout or other protected action | Call `identify()` in that action's handler, or use the SDK's supported early-interaction helper, and send its `identificationId` with the action. |

`identify()` forces a fresh identification with a new UUID and can consume included volume. Do not put it on every page load, render or route change for passive collection. `check()` can return `null` when the agent skips the check; this is not an SDK failure. The agent's five-minute interval applies within a visit, not across all full page navigations: a full page transition can end the visit and allow another check sooner. Do not promise a fixed number of identifications for several pages or add a custom throttle unless requested. Reuse a captured UUID when verifying; do not repeatedly force new identifications just to get a result.

For selected server verification, retrieve the action's ID using the chosen server SDK. In Node.js, create `new ShieldLabs({ apiKey })`, select the domain UUID from `entities.domains()` and call `identifications.get(id, { domainId })`. The domain UUID belongs in each read's options, not in the constructor. Add freshness, replay and risk checks. For a frontend-only website or app, verify through MCP or the dashboard; add a backend only if requested.

Before running the browser integration, inspect the effective Content Security Policy in app/server/CDN response headers and HTML meta tags. For the default SDK environment, merge these exact permissions into an existing policy:

| Directive | ShieldLabs sources |
| --- | --- |
| `script-src` (and `script-src-elem` when present) | `https://cdn.shieldlabs.ai` |
| `connect-src` | `https://rest.shieldlabs.ai wss://rest.shieldlabs.ai https://webrtc.shieldlabs.ai stun:ice.shieldlabs.ai:3478` |
| `img-src`, only when using the optional noscript beacon | `https://rest.shieldlabs.ai` |

Preserve existing sources, nonces, hashes and strict-policy behavior. If the project explicitly selects another SDK environment or script URL, inspect its actual endpoints and the [CSP guide](https://docs.shieldlabs.ai/setup/csp) rather than substituting guessed hosts. Use a framework module, external bootstrap or nonce/hash for inline code; do not add broad wildcards, `unsafe-inline` or `unsafe-eval` to make the SDK work. An app without CSP does not need a new policy just to install ShieldLabs.

### Read current History

MCP History reads use `domain` as the public domain UUID from `shieldlabs_list_domains`, never the hostname:

- `shieldlabs_list_history` lists `resource="identifications"` or `resource="user"`.
- `shieldlabs_get_history` reads one `identifications`, `user`, `visitor`, `device` or `ip` card by `resource_id`.
- Filters are flat tool arguments. Use only the fields in the connected tool's schema, such as `risk_bands`, `risk_signals`, `source`, `campaign` and `banned_ip`; `ids` applies only to the user list and takes one User HID string. Pass one value, not an array or repeated query key. Do not send nested `filter`, `chips`, `offset`, `include_total` or `sort`.
- Follow `next_page_token` with `page_token`, keeping the same resource, window, filters and limit. When using `from` and `to`, send both as RFC3339 timestamps; the window includes `from` and excludes `to`.

Node.js 1.3.2 exposes the same domain-scoped reads through `entities.list(domainId, resource, options)` and `entities.get(domainId, resource, id, options)`. Use these instead of retired `history.search` or `history.iterate` calls.

Identification cards expose `id`, `risk.state`, `risk.score` and `signals.state`/`signals.items`. Unavailable risk is unknown, never Score 0. Preserve missing identifiers, nullable fields and signal/HRE availability; do not turn them into negative flags or invent signal weights. `banned_ip` is the canonical public Banned IP signal; HTTP 429 keeps its separate `rate_limited` error code. A 999 marker is not a Risk Score or a fourth band. The three Risk Score bands are Trusted 0–29, Suspicious 30–59 and Dangerous 60–100. Account High-Risk Events and their Medium/High confidence are a separate axis from Risk Score.

## Step 5: Verify

Start the website or app on the registered HTTPS hostname. For passive collection, load the page normally; for action verification, trigger the selected action. Verify the installed integration rather than injecting a replacement SDK call into the browser. If an ad blocker prevents SDK loading, ask the user to allow it for testing.

Check the browser console for ShieldLabs CSP violations and the network panel for successful module loading and signal submission. Resolve blocked SDK requests before marking the browser stage verified.

For a newly created UUID, allow a total of 10–15 seconds for asynchronous processing, including read waits. This is a bounded setup verification step, not a fixed delay to add to the website's business logic. A browser UUID alone does not prove the snapshot was accepted or scored.

For **Path A**, call `shieldlabs_get_identification` with the public domain UUID as `domain`, the browser UUID as `identification_id` and `wait=true` (the tool polls for at most 10 seconds). If still missing, use only the remaining time in the 10–15 second budget, then make one non-waiting read of the same card through `shieldlabs_get_history` with `resource="identifications"` and `resource_id` equal to that UUID. Stop after this bounded check; do not call `identify()` again for a missing row. A missing card or `risk.state="unavailable"` remains unverified; do not call it a successful or clean identification. Match the card's `id` to the browser UUID and, when present, the backend result. Report Risk Score only when `risk.state="ok"`. Call `shieldlabs_explain_risk_score` for the same identification and explain only returned signals and available weights.

For **Path B**, allow the same processing time, then ask the user to confirm the browser UUID and Risk Score in the dashboard, matching the backend result when present. MCP is not required.

Separate a pending History result from a rejected browser submission. Keep endpoint, HTTP status, UUID and timing evidence in a local report without secrets. An HTTP 402 or 503 from those endpoints is not explained by querying History too early. Do not infer exhausted volume or ask the user to change billing from a missing card or a single response alone. Inspect authorized response details and account state; if the cause remains unresolved, say which stage failed and what action is needed. After the bounded wait, report persistent failures clearly without flooding the user with raw diagnostics. A later successful check with a different UUID does not establish success for the failed UUID. Avoid repeated force calls or page reloads while investigating.

Report the chosen path, domain, SDK versions, UUID and Risk Score. Explain blockers. Mark only the verified stage complete; browser identification alone does not establish server-side protection.

## Step 6: Choose what to do next

After verifying a stage, ask **“What would you like to set up next?”** Offer only applicable unfinished stages:

- **Server-side verification.** If the backend is in another repository, ask for its path. Do not create a backend without a request.
- **Signed webhooks.** Ask for the authorized public HTTPS receiver if unknown. Create and verify the endpoint through MCP or the dashboard, then follow the webhook verification steps below.
- **Signed-in user linking and account-abuse checks.** The app supplies its authenticated account's internal ID; the SDK cannot discover who is signed in. Compute a stable User HID on the server with the server SDK's HMAC helper (`userHid(accountId, secret)` in Node.js). The helper performs the hash automatically once given the account ID and secret. Pass only the resulting HID to the browser SDK as `userId`; keep the HMAC secret on the server and never send raw account IDs or emails to ShieldLabs. Verify the returned `user_hid` against the authenticated session and read the account's `user` card with `shieldlabs_get_history` or `entities.get(domainId, 'user', userHid)`. Preserve HRE availability; Medium/High confidence is separate from Risk Score. Basic anonymous visitor identification does not require User HID. Verify account-level history and any selected group delivery separately from the browser identification stage. Keep custom History policies distinct.
- **Finish for now.** Report completed stages and what remains unconfigured; do not continue after this choice.

Run the chosen stage, verify its result, then offer the remaining choices. If the user already selected the next stage or all applicable stages, continue without repeating the menu. A failed check pauses the next stage: explain it and resolve it within the authorized scope before proceeding.

### Verify signed webhooks

1. Keep the signing secret on the server. Verify `X-Shield-Signature` against the exact raw request bytes before parsing or changing the body. In Node.js 1.3.2 use `webhooks.constructEvent(rawBody, signature, secret)`; in edge runtimes use `await webhooks.constructEventAsync(rawBody, signature, secret)`.
2. Dispatch by `String(event.event_type)`. Handle `identification.scored`, `webhook.ping` and `hre.multi_account.changed` separately. The SDK exposes group fields through `event.raw.data`; use that object rather than the scored event's `event.data` shape. Preserve other valid event types without treating them as identification results.
3. Durably queue verified events before acknowledging success and deduplicate by `event.raw.event_id`. Group events have no identification UUID; compare revisions numerically per cluster, not as strings.
4. Every enabled webhook endpoint with a public HTTPS URL receives multi-account group events. The stored `multiaccount` flag does not gate delivery. Handle group events even when the endpoint was originally added for scored events.
5. Verify a real scored delivery from the installed integration. A verification ping confirms the receiver only. If group delivery is selected, verify an actual `hre.multi_account.changed` event and its signature separately; ordinary scored events or the presence of linked accounts do not establish group delivery.

For crawler/AI decisions distinguish unverified identity claims from proof of a specific
provider/service attribute. Missing attribution is unknown. Keep order/promo context in your
own backend, joined by domain and identification UUID; group events have no action UUID.
Use the focused [skills](https://github.com/ShieldLabs-ai/skills) for the selected stage.

## Resources

- [Quick start](https://docs.shieldlabs.ai/quickstart)
- [SDK guides](https://docs.shieldlabs.ai/api/sdks)
- [Documentation for agents](https://docs.shieldlabs.ai/llms.txt)
- [Start Free](https://app.shieldlabs.ai)
