Detect: Beacon Integration
Add client-side signal collection for AI agent detection to your JavaScript application
Goal
Integrate the Checkpoint JavaScript 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 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-beaconyarn add @kya-os/checkpoint-beaconpnpm add @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;
}// composables/useCheckpointBeacon.ts
import { onMounted, onUnmounted, ref } from 'vue';
import { CheckpointBeacon } from '@kya-os/checkpoint-beacon';
export function useCheckpointBeacon() {
const beacon = ref<CheckpointBeacon | null>(null);
onMounted(() => {
beacon.value = new CheckpointBeacon({
projectId: import.meta.env.VITE_CHECKPOINT_PROJECT_ID,
});
beacon.value.collect('pageview');
});
onUnmounted(() => {
beacon.value?.destroy();
});
const track = (name: string, metadata?: Record<string, unknown>) => {
beacon.value?.trackEvent(name, metadata);
};
return { track };
}Use it in a component:
<script setup lang="ts">
import { useCheckpointBeacon } from '@/composables/useCheckpointBeacon';
const { track } = useCheckpointBeacon();
function handleFormSubmit() {
track('form_submit', { formId: 'contact' });
}
</script>
<template>
<button @click="handleFormSubmit">Submit</button>
</template>// detection.ts
import { CheckpointBeacon } from '@kya-os/checkpoint-beacon';
const beacon = new CheckpointBeacon({
projectId: 'acme-corp',
debug: true,
logLevel: 'debug',
});
// Record the initial page view
beacon.collect('pageview');
// Track custom events
document.getElementById('checkout-btn')?.addEventListener('click', () => {
beacon.trackEvent('checkout_started', {
cartValue: 99.99,
itemCount: 3,
});
});
// Flush reliably on unload
window.addEventListener('pagehide', () => {
beacon.trackPageUnload();
});
export { beacon };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 automatic collections/flushes (default 5000)
batchSize: 10, // events per batch (default 10)
maxQueueSize: 100, // max queued events (default 100)
// Reliability
retryAttempts: 3, // default 3
retryDelay: 2000, // base retry delay in ms (default 2000)
// Privacy
respectDoNotTrack: true, // honor the browser DNT signal (default true)
// Performance — Web Worker is auto-disabled in local dev
useWorker: true,
// Debugging
debug: process.env.NODE_ENV === 'development',
logLevel: 'error', // set to 'debug' to see verbose logs
});There is no endpoint, autoStart, batchInterval, compression, or timeout option. The send
interval is flushInterval; the destination is fixed to https://kya.vouched.id/v1/beacon.
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'],
});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/v1/beacon - 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/v1/beacon after you call collect()/trackEvent() (and on the periodic heartbeat). 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
| Symptom | Cause | Fix |
|---|---|---|
Project ID is required throw | Missing projectId | Set projectId; check the env var and restart the server |
| No network requests | Do Not Track is on | The 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 requests | Nothing triggered a send yet | Call collect('pageview') / trackEvent(...), or wait for the flushInterval heartbeat |
| Worker error in dev | Bundler can't serve the worker | Workers auto-disable in dev; set useWorker: false to silence it |
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
Next Steps
| Goal | Next Cookbook |
|---|---|
| Block detected agents | Middleware Enforcement |
| DNS-level enforcement | Gateway Setup |
| Server-side detection | Middleware Detection |