Cookbooks

Detect: Beacon Integration

Add client-side signal collection for AI agent detection to your JavaScript application

Goal

Integrate the Checkpoint Beacon into your web application to collect client-side signals for AI agent detection. By the end of this cookbook, you'll have:

  • Client-side signal collection, with optional Web Worker offloading
  • Custom event tracking
  • Automatic offline resilience with batched uploads
  • Full TypeScript support

The Beacon collects signals and sends them to Checkpoint; classification happens server-side. There is no client-side detection result — you view classifications in the dashboard. If you need a verdict inline in a request, use Middleware or the Gateway.

Best for: React, Vue, Angular, and other SPAs where you want rich client-side signals plus custom event tracking.

Prerequisites

  • A Checkpoint account with a project created
  • A JavaScript/TypeScript application
  • Node.js 16+ and npm/yarn/pnpm

Time Estimate

15 minutes


Steps

Install the Beacon Package

npm install @kya-os/checkpoint-beacon

Get Your Project ID

Find your Project ID on Installations — see Credentials for the exact click-path and ID format.

Add it to your environment variables:

# .env.local (Next.js)
NEXT_PUBLIC_CHECKPOINT_PROJECT_ID=acme-corp

# .env (Vite)
VITE_CHECKPOINT_PROJECT_ID=acme-corp

# .env (Create React App)
REACT_APP_CHECKPOINT_PROJECT_ID=acme-corp

Use the same Project ID as your pixel — the Beacon and Pixel report into the same project.

Initialize the Beacon

Create a small hook that initializes the Beacon, records a page view, and exposes a track helper:

// hooks/useCheckpointBeacon.ts
import { useEffect, useRef } from 'react';
import { CheckpointBeacon } from '@kya-os/checkpoint-beacon';

export function useCheckpointBeacon() {
  const beaconRef = useRef<CheckpointBeacon | null>(null);

  useEffect(() => {
    const beacon = new CheckpointBeacon({
      projectId: process.env.NEXT_PUBLIC_CHECKPOINT_PROJECT_ID!,
      debug: process.env.NODE_ENV === 'development',
      logLevel: process.env.NODE_ENV === 'development' ? 'debug' : 'error',
    });
    beaconRef.current = beacon;

    // Record the initial page view
    beacon.collect('pageview');

    return () => {
      beacon.destroy(); // flushes pending events
    };
  }, []);

  const track = (name: string, metadata?: Record<string, unknown>) => {
    beaconRef.current?.trackEvent(name, metadata);
  };

  return { track };
}

Expose it through context so any component can send events:

// components/CheckpointProvider.tsx
'use client';

import { createContext, useContext, ReactNode } from 'react';
import { useCheckpointBeacon } from '@/hooks/useCheckpointBeacon';

const BeaconContext = createContext<ReturnType<typeof useCheckpointBeacon> | null>(null);

export function CheckpointProvider({ children }: { children: ReactNode }) {
  const beacon = useCheckpointBeacon();
  return <BeaconContext.Provider value={beacon}>{children}</BeaconContext.Provider>;
}

export function useBeacon() {
  const context = useContext(BeaconContext);
  if (!context) throw new Error('useBeacon must be used within CheckpointProvider');
  return context;
}

Configure Advanced Options

Customize the Beacon for your use case (see the full options table):

const beacon = new CheckpointBeacon({
  // Required
  projectId: process.env.NEXT_PUBLIC_CHECKPOINT_PROJECT_ID!,

  // Segmentation
  environment: process.env.NODE_ENV,
  tags: { version: '2.0.0' },

  // Collection & batching
  flushInterval: 5000, // ms between heartbeats and flushes (default 5000)
  batchSize: 10, // events per request (default 10)
  maxQueueSize: 100, // batches kept while offline (default 100)

  // Reliability
  retryAttempts: 3, // attempts per batch, including the first (default 3)
  retryDelay: 2000, // base retry delay in ms, doubling per attempt (default 2000)

  // Privacy
  respectDoNotTrack: true, // honor the browser DNT signal (default true)
  stripUrlFragment: true, // drop #fragment from url and referrer (default true)

  // Web Worker: opt-in, off by default. Needs a same-origin worker script with a bundler.
  // useWorker: true,
  // workerUrl: '/static/beacon.worker.js',

  // Debugging (npm build only; the script tag build strips console output)
  debug: process.env.NODE_ENV === 'development',
  logLevel: 'error', // set to 'debug' to see verbose logs
});

There is no autoStart, batchInterval, compression, or timeout option. The send interval is flushInterval; the destination defaults to https://kya.vouched.id/api/v1/beacon and can be changed with endpoint. With a bundler, the Web Worker and the lazy signals chunk need workerUrl and lazySignalsUrl; see Web Worker Mode and Data Collection.

Add Event Tracking

Send custom events with trackEvent(name, metadata):

// Track a page view for a specific route (e.g. on SPA navigation)
beacon.collect('pageview');

// Track user actions
beacon.trackEvent('button_click', {
  buttonId: 'signup-cta',
  variant: 'hero',
});

// Track form submissions
beacon.trackEvent('form_submit', {
  formId: 'contact-form',
  fields: ['name', 'email', 'message'],
});

Each call adds a detection sample at that moment. The event name and metadata are sent and validated, but the ingest endpoint does not persist them today, so they will not show up in the dashboard; see trackEvent().

Verify Installation

  1. Start your development server
  2. Open the browser console — with logLevel: 'debug' you'll see Beacon logs
  3. Check the Network tab for a POST to https://kya.vouched.id/api/v1/beacon with a 202 response
  4. Visit your Checkpoint dashboard → Analytics

You should see detections flowing in within seconds.


Verify It's Working

Console Verification

With debug: true and logLevel: 'debug', you'll see logs prefixed [Checkpoint Beacon]:

[Checkpoint Beacon] Beacon initialized { sessionId: '…', useWorker: false }
[Checkpoint Beacon] Data collected and sent { type: 'pageview', … }

Setting debug: true alone isn't enough to see informational logs — the log level defaults to error. Set logLevel: 'debug' (or 'info') to see initialization and send logs.

Network Verification

Open DevTools → Network and confirm the Beacon is POSTing to https://kya.vouched.id/api/v1/beacon after you call collect()/trackEvent() (and on the periodic heartbeat). A 202 means the batch was accepted; 404 means the Project ID is unknown. If you see no requests, check the troubleshooting table below.

Confirm Classification in the Dashboard

Classification happens server-side. After sending events, open the dashboard Analytics tab and find your session to see how Checkpoint classified the traffic (human, ai_agent, bot, or incomplete_data). Spoofing a User-Agent in the browser does not produce a client-side class — the Beacon has no client-side verdict.


Troubleshooting

Beacon Not Sending

SymptomCauseFix
Project ID is required throwMissing projectIdSet projectId; check the env var and restart the server
No network requestsDo Not Track is onThe Beacon honors DNT by default — it sends nothing when the browser signals DNT. Set respectDoNotTrack: false only if appropriate for your use case.
No network requestsNothing triggered a send yetCall collect('pageview') / trackEvent(...), or wait for the flushInterval heartbeat
Worker never startsNo same-origin worker scriptWorkers are off by default. With useWorker: true and a bundler, host beacon.worker.js yourself and pass workerUrl, or leave useWorker off

Events Not Appearing

  • Batching — events are batched and flushed on the flushInterval. Call beacon.destroy() on teardown to flush pending events (there is no stop() method).
  • Offline — events queue when offline (up to maxQueueSize) and send on reconnect.
  • Server-side delay — allow a few seconds for detections to appear in the dashboard.

TypeScript

Type definitions ship with the package — no extra @types install is needed. Just import CheckpointBeacon (and any exported types) from @kya-os/checkpoint-beacon.


What You Learned

  • How to install and initialize the Beacon SDK (it auto-starts collection on construction)
  • How to record page views with collect() and custom events with trackEvent()
  • How to flush on teardown with destroy()
  • That classification is server-side — viewed in the dashboard, not returned on the client

Next Steps

GoalNext Cookbook
Block detected agentsMiddleware Enforcement
DNS-level enforcementGateway Setup
Server-side detectionMiddleware Detection