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

# JavaScript

> Identify a visitor, send a Request ID with your application action and verify the result on your server.

Identify a visitor, send a Request ID with your application action and verify the result on your server. This guide uses the supported ShieldLabs package API, not a standalone generated client.

## Before you start

A modern browser, a registered HTTPS domain and a Public Key. Node.js is needed only for the package-manager setup.

Register and verify the website domain in your [analytics dashboard](https://app.shieldlabs.ai/). Use credentials from the same domain and environment as the browser check. For a fresh account, start with [Quick Start](/quickstart).

## Get your credentials

Copy the Public Key for your domain from **Integration > API keys**. A public frontend environment variable may contain this key, but never a Private API Key or webhook signing secret.

## Install the SDK

```bash theme={null}
npm install @shieldlabs-ai/js
```

Use `yarn add` or `pnpm add` with the same package names if your project uses that package manager. Framework packages remain the application's responsibility.

## Add the integration

```ts theme={null}
import { load, type LoadOptions } from '@shieldlabs-ai/js';

const options: LoadOptions = { publicKey: import.meta.env.VITE_SHIELDLABS_PUBLIC_KEY };

// Start loading the agent now, but do not await it here: the form must keep working when the
// agent cannot load (a content blocker, a network error).
load(options).catch(() => {}); // handled in the submit handler

const form = document.querySelector<HTMLFormElement>('#signup')!;
form.addEventListener('submit', async (event) => {
  event.preventDefault();
  let requestId: string | null = null;
  try {
    // load() again: it returns the loaded agent at once, waits for a load that is still running
    // and tries again after a failed one.
    const agent = await load(options);
    ({ requestId } = await agent.identify());
  } catch {
    // No identification: send the signup anyway. Your server treats it as unverified.
  }
  await fetch('/api/signup', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: form.email.value, requestId }),
  });
});
```

Avoid a top-level `await load(...)`. When the agent is blocked, the rest of the module never runs,
so the submit handler is never attached. Some build targets also reject top-level `await`. Call
`load()` again wherever you need the agent rather than keeping its first promise: that promise stays
rejected after a passing network error or a slow load, and a new call recovers from both.

On your server, read the verdict for `requestId` with a ShieldLabs server SDK, for example
`identifications.get(requestId)` in [`@shieldlabs-ai/node`](https://github.com/ShieldLabs-ai/shieldlabs-node),
which waits until the identification has been scored. The History row appears about 1 to 3 seconds
after the browser call and can be refined for up to about 10 seconds while follow-up checks finish,
so start the identification when the user begins the action, for example with
`identifyOnInteraction()` (see [Protect a form ](https://github.com/ShieldLabs-ai/shieldlabs-js/blob/deab677a33fdc7f20578c829fa1698db68552557/README.md#protect-a-form)).

> **Keep the page alive after `identify()` resolves.** The agent posts the identification right
> after it hands over the request ID. Keep the page open until your own request has been sent, and
> do not navigate away the moment `identify()` resolves (for example with `location.href = ...` in
> its `then` callback): the agent's post may not have gone out yet. For classic full-page form
> posts, start the identification early with `identifyOnInteraction()` (see the guide).

> **Test on a registered domain.** ShieldLabs records identifications only for the domains
> registered in your account. On `localhost` the page still receives a `requestId`, but the
> identification is rejected with `401` and your backend never finds it. Test on a development
> domain with its own keys, as described in [Environments](https://docs.shieldlabs.ai/setup/environments).

## Test the complete flow

1. Run the application on the registered HTTPS domain with its Public Key.
2. Trigger the protected form once and check that a Request ID is sent to your own backend.
3. Retrieve that same ID using a server SDK or locate it in the analytics dashboard.
4. Test a missing ID and an agent load failure: your backend must treat the action as unverified.

The `/api/signup` routes shown in examples belong to your application; they are not ShieldLabs API endpoints. Connect one of the [server quick starts](/api/sdks#server-packages) before testing the full action. A successful browser call does not prove scoring is complete.

## Troubleshooting

* No History row: confirm the registered domain, credential/environment match and that the browser remained open while collectors posted.
* Missing or pending verdict: scoring is asynchronous. The server helper waits within a bounded budget; handle a missing result and API errors explicitly.
* Authentication error: use the Private API Key for History, not a Public Key, Management Secret Key or MCP OAuth token.
* Invalid webhook signature: verify the original raw bytes with the endpoint's full signing secret, before trusting parsed JSON.

## Next steps

* [Runnable example](https://github.com/ShieldLabs-ai/shieldlabs-js/tree/deab677a33fdc7f20578c829fa1698db68552557/examples/vanilla)
* [SDK reference and changelog](https://github.com/ShieldLabs-ai/shieldlabs-js)
* [Identification flow](/api/identification-flow)
* [Server API](/api/server-api)
* [Webhook setup](/setup/webhooks)
* [Content Security Policy](/setup/csp)


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