# Install FindIP Shield: recipe for coding agents

This page is written for an AI coding agent (Claude Code, Cursor, Copilot, Codex or similar) that has been asked to add FindIP Shield to a project. It is deterministic: detect, choose one route, make the smallest change, verify, stop. A human reads the result; do not do more than the steps say.

FindIP Shield is a browser SDK plus dashboard that scores website visits and form submits for VPN, proxy, Tor, relay, hosting, datacenter and malicious-IP signals. It reports; enforcement is configured later by the site owner. It never collects passwords, payment fields, form values, raw emails or phone numbers.

## 0. What you need from the user

- A **public site key** (`pub_...`). If the user's prompt already gives it (the Shield Install page's "Let your AI assistant do it" prompt does, together with the site's domain), use that key and do not ask again. Otherwise it comes from the Shield dashboard after creating a site for the domain: https://www.findip.net/shield (sign-in required). If the user has no key, stop and ask for one; do not invent or hard-code a placeholder in committed code.
- Optionally a **secret key** (`sec_...`) for server verification (step 4). It must only ever live in server-side configuration.

Store the public key in the project's existing configuration mechanism (environment variable, config file, CMS setting). Suggested names: `FINDIP_SHIELD_SITE_KEY` (server), `NEXT_PUBLIC_FINDIP_SHIELD_SITE_KEY` / `VITE_FINDIP_SHIELD_SITE_KEY` (bundler-exposed), `FINDIP_SHIELD_SECRET_KEY` (server only, never exposed).

## 1. Detect the project and pick one route

Check, in order, and take the first match:

| If the project is | Route | Go to |
|---|---|---|
| A Shopify theme or store | Do not add code. Tell the user to install the app from https://apps.shopify.com/findip-shield and paste the site key there. | stop |
| A WordPress site (wp-config.php, wp-content/) | Do not add code. Tell the user to install the "FindIP Shield" plugin from WordPress.org and paste the site key in its settings. WooCommerce context is added automatically. | stop |
| Managed through Google Tag Manager only (no app code access) | Use the GTM template: https://www.findip.net/docs/shield/google-tag-manager.md | stop |
| A bundled JavaScript app (package.json with React, Next.js, Vue, Nuxt, SvelteKit, Astro, Vite, Angular) | **npm package** | step 2A |
| Server-rendered or static HTML (Razor, Blade, Django, Rails, Jinja, PHP, plain HTML) | **script tag** | step 2B |

Shield runs in the browser. Do not import it in server-only code, API routes, edge functions or build scripts.

## 2A. npm route

```bash
npm install @findip/shield
```

Initialise once, in client-side code that runs on every page after the app mounts. The package exports `init`, `track`, `identify`, `getSession`, `setConsent` and `version`, as ESM and CommonJS with TypeScript types.

```ts
import { init } from '@findip/shield';

init({
  siteKey: process.env.NEXT_PUBLIC_FINDIP_SHIELD_SITE_KEY!, // or import.meta.env.VITE_..., or your config
  privacyMode: 'balanced',   // 'strict' | 'balanced' | 'advanced'; keep the default unless told otherwise
  autoTrack: true,           // page views
  autoDetectForms: true,     // signup, login, checkout and lead forms by their attributes
});
```

Where to put it:

- **Next.js App Router**: a small `'use client'` component rendered once in `app/layout.tsx`, calling `init` inside `useEffect`.
- **Next.js Pages Router**: `useEffect` in `pages/_app.tsx`.
- **React + Vite**: `main.tsx` before `createRoot`, or `useEffect` in the root component.
- **Vue / Nuxt**: a client-only plugin (`plugins/findip-shield.client.ts` in Nuxt).
- **SvelteKit**: `onMount` in the root `+layout.svelte`.
- **Astro**: a `<script>` in the base layout, or the script tag route below.

Guard against server rendering: `init` touches `window` and `document`; call it only in the browser.

Optional, only if the user asked for it: name the business action explicitly where the automatic form detection would not, for example after a programmatic submit:

```ts
import { track } from '@findip/shield';
await track('signup_attempt', { plan: 'free' });
```

Allowed context fields are `user_id_hash`, `email_hash`, `email_domain`, `account_age_days`, `plan`, `transaction_amount`, `currency`, `form_name`, `lead_source`, `custom`. Never pass raw emails, names, passwords or card data.

## 2B. Script tag route

Add one tag before the closing `</body>` in the shared layout, with the site key substituted from configuration:

```html
<script
  src="https://cdn.findip.net/shield/v1.js"
  data-site-key="pub_xxxxxxxxx"
  data-auto-track="true"
  data-privacy-mode="balanced">
</script>
```

`v1.js` always serves the latest non-breaking 1.x build. If the project pins third-party scripts or uses a strict Content Security Policy, use the pinned build with subresource integrity instead; the current hash is on the site's Install page in the dashboard and in the quickstart:

```html
<script
  src="https://cdn.findip.net/shield/1.11.0/findip-shield.min.js"
  integrity="sha384-ldLD3k3PmChiCgYb8arImgAeTQxmL3gDwPmWJ12yoIwceKbZdstQyjLPjCFpnBJ2"
  crossorigin="anonymous"
  data-site-key="pub_xxxxxxxxx"
  data-auto-track="true"
  data-privacy-mode="balanced">
</script>
```

If the site has a CSP, allow `script-src https://cdn.findip.net` and `connect-src https://shield.findip.net`.

The CDN build auto-initialises from `data-*` attributes and exposes `window.FindIP` with the same methods as the npm package. Script-tag options: `data-site-key` (required), `data-privacy-mode`, `data-auto-track`, `data-auto-detect-forms`, `data-push-to-data-layer`, `data-session-field`, `data-link-session`, `data-debug`.

## 3. Verify the install

1. Run the site locally (or open the deployed site) and load one page in a browser.
2. In DevTools, Network tab, filter for `shield/track`. A `POST` to `https://shield.findip.net/v1/shield/track` after page load means the SDK is sending.
3. Check the install status for the key, which needs no sign-in:

   ```
   GET https://shield.findip.net/v1/shield/install-status?site_key=pub_...
   ```

   It answers JSON with `state` and a plain-text `message`. Act on the state and pass the message to the user:

   | `state` | Meaning | What to do |
   |---|---|---|
   | `test_visit` | A visit from `localhost` / `127.0.0.1` arrived. The SDK works; local visits are never stored. | Done locally. Tell the user to deploy; the first real visit completes the install. |
   | `receiving` | Visits from the real domain are arriving. | Done. |
   | `refused` | An event was refused; `refusal.reason` says why (`domain_mismatch`, `missing_origin`, `site_disabled`, ...). | Follow `message`. Fix the code only if the cause is in the code; otherwise tell the user what to change in the dashboard. |
   | `waiting` | Nothing has arrived yet. | Wait a few seconds after loading a page and check once more. Then look at the Network tab: an ad blocker, a Content-Security-Policy without `https://shield.findip.net` in `connect-src`, or a missing key are the usual causes. |

   The answer is cached for 10 seconds and rate-limited; check a few times at most, do not poll in a loop.

**Testing on localhost:** until the site's first real visit, a visit from `localhost`, `127.0.0.1` or `*.localhost` counts as a test visit (response `202`, `test_visit: true`). It is not stored and is not a real install. After the first real visit, localhost is refused again as `domain_mismatch`. Do not add `localhost` as a site domain.

If nothing is sent on the real domain: the domain must be allowlisted for the site key (dashboard, site settings), the origin must match, and in `strict` privacy mode no visitor cookie is expected.

Report to the user what was changed, where the key is read from, how you tested it (local or deployed), what the install status said, and that the site is now in monitor-only mode: nothing is enforced until they configure a response per form in the dashboard.

## 4. Optional: server verification (only if asked, or if the task is to protect signup, login or payment)

The browser result is informational. For a decision that matters, the server verifies the session with the secret key before acting. The browser SDK can carry the session ID into your request:

- Form posts (SDK 1.11.0+): `init({ ..., sessionField: true })` (or `data-session-field="true"`) adds a hidden input `findip_session` to forms that POST to the page's own origin.
- Fetch/XHR: `const { sessionId } = getSession();` and include it in the request body.

Server side, before creating the account, issuing the session token or capturing payment:

```http
POST https://shield.findip.net/v1/shield/sessions/verify
Authorization: Bearer <FINDIP_SHIELD_SECRET_KEY>
Content-Type: application/json

{ "session_id": "<session id from the request>", "event": "signup_attempt" }
```

Response fields to act on: `verified` (boolean), `risk_status` (`available` or `unknown`), `risk_score` (0 to 100), `recommendation` (`allow`, `monitor`, `challenge`, `block`), `challenge_passed` (true once the visitor completed a Turnstile challenge Shield verified), and `session.network` flags (`vpn`, `proxy`, `tor`, `relay`, `hosting`, `datacenter`, `malicious`).

Suggested minimal policy, unless the user specifies one: reject `block`; accept `challenge` only when `challenge_passed` is true; treat `risk_status: unknown` as `monitor`, never as safe; log everything else and let it through. Keep the secret key out of client code and out of version control.

Full reference: https://www.findip.net/docs/shield/server-verification.md

## 5. Do not

- Do not block or redirect visitors from code because a flag such as `vpn` is true. A risk score is an assessment, not proof of abuse; per-form responses are configured by the site owner in the dashboard, not hard-coded.
- Do not send form values, passwords, emails, phone numbers or card data to `track()`. To attach a logged-in user, use `identify({ userId, email, plan })`; the SDK hashes the values in the browser.
- Do not put the secret key in client code, in `NEXT_PUBLIC_` / `VITE_` variables, or in the repository.
- Do not add the SDK to server-side, build-time or edge code paths.
- Do not add a second copy if an install already exists (search for `@findip/shield`, `cdn.findip.net/shield`, or `FindIP.init`).

## References

- Quickstart: https://www.findip.net/docs/shield/quickstart.md
- SDK reference: https://www.findip.net/docs/shield/javascript-sdk.md
- Events: https://www.findip.net/docs/shield/events.md
- Data collection and privacy modes: https://www.findip.net/docs/shield/data-collection.md · https://www.findip.net/docs/shield/privacy-modes.md
- Everything in one file: https://www.findip.net/llms-full.txt
- Package: https://www.npmjs.com/package/@findip/shield · Source: https://github.com/findip-net/findip-shield-js
- Working examples for Next.js, React, Vue, Astro, SvelteKit and plain HTML, and this recipe as a Claude Code skill and a Cursor rule: https://github.com/findip-net/findip-shield-examples

---

Canonical page: https://www.findip.net/docs/shield/agent-install.md
