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-beaconGet 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-corpUse 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
- Start your development server
- Open the browser console — with
logLevel: 'debug'you'll see Beacon logs - Check the Network tab for a POST to
https://kya.vouched.id/api/v1/beaconwith a202response - 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
Events Not Appearing
- Batching — events are batched and flushed on the
flushInterval. Callbeacon.destroy()on teardown to flush pending events (there is nostop()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 withtrackEvent() - How to flush on teardown with
destroy() - That classification is server-side — viewed in the dashboard, not returned on the client
