JavaScript Beacon
Full-featured client-side signal collection for AI agent detection, with Web Worker offloading
Overview
The Checkpoint JavaScript Beacon is a client-side SDK that collects browser and performance signals and sends them to Checkpoint for AI agent detection. It runs non-blocking, batches events, offloads work to a Web Worker when available, and queues events while offline.
The Beacon collects signals — classification happens server-side. It does not return a detection class on the client. View results in the dashboard. If you need a detection classification inline in a request, use server-side Middleware or the Gateway instead.
Key Features
- Non-blocking — Collection runs on an interval and offloads to a Web Worker when available
- Automatic batching — Efficient network usage with smart queue management
- Offline resilience — Queues events when offline, sends when reconnected
- Privacy-first — Honors Do Not Track by default; optional IP anonymization
- TypeScript — Full type definitions included
Prerequisites
- A Checkpoint Project ID — the constructor throws if it's missing. See Credentials for where to find it in the dashboard.
- A JavaScript-executing browser context (SPA, static site, or any client-side app) — the Beacon has no server-side component.
Installation
npm install @kya-os/checkpoint-beaconyarn add @kya-os/checkpoint-beaconpnpm add @kya-os/checkpoint-beaconQuick Start
import { CheckpointBeacon } from '@kya-os/checkpoint-beacon';
const beacon = new CheckpointBeacon({
projectId: 'acme-corp', // same value as the pixel's data-project-id
});
// Record a page view (a periodic heartbeat also runs automatically)
beacon.collect('pageview');projectId is required — the constructor throws if it is missing. Use the same Project ID as
your pixel (a name-based slug like acme-corp, or a UUID). There is no endpoint option; the
Beacon POSTs to https://kya.vouched.id/v1/beacon automatically.
How It Works
Constructing the Beacon immediately starts a periodic collection loop (a heartbeat every flushInterval). Call collect('pageview') or trackEvent(...) to record specific events. For each send, the Beacon picks the best available execution mode:
- Web Worker mode (when
useWorkeris enabled and the browser is capable) — processing runs off the main thread - Fallback mode — main-thread execution scheduled via
requestIdleCallback - Direct mode — a direct transport send
Browser capabilities are detected automatically; if the Worker is unavailable or insufficient, the Beacon silently falls back.
Configuration
const beacon = new CheckpointBeacon({
// Required
projectId: 'acme-corp',
// Segmentation
environment: 'production',
tags: { version: '2.0.0', region: 'us-east' },
// Collection & batching
flushInterval: 5000, // ms between automatic collections/flushes
batchSize: 10, // events per batch
maxQueueSize: 100, // max events queued (e.g. while offline)
// Reliability
retryAttempts: 3,
retryDelay: 2000, // base retry delay in ms
// Privacy
respectDoNotTrack: true, // honor the browser DNT signal (default)
anonymizeIp: false,
// Worker
useWorker: true, // auto-disabled in local dev — see below
// Debugging
debug: false,
logLevel: 'error',
});Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
projectId | string | Required | Project ID (same value as the pixel's data-project-id) |
environment | string | — | Environment tag for segmentation |
tags | Record<string, string | number | boolean> | — | Custom segmentation tags |
enabled | boolean | true | Master on/off switch |
debug | boolean | false | Verbose console logging |
logLevel | 'error' | 'warn' | 'info' | 'debug' | 'error' | Log verbosity |
respectDoNotTrack | boolean | true | When the browser sends DNT, the Beacon collects/sends nothing |
anonymizeIp | boolean | false | Request IP anonymization |
sessionTimeout | number | 1800000 | Session lifetime in ms (30 minutes) |
sessionCookieName | string | '_as_beacon_session' | Session cookie name |
batchSize | number | 10 | Events per batch |
flushInterval | number | 5000 | Interval (ms) for automatic collection/flush |
maxQueueSize | number | 100 | Max queued events |
retryAttempts | number | 3 | Retry count for failed sends |
retryDelay | number | 2000 | Base retry delay (ms) |
useWorker | boolean | environment-derived | Use a Web Worker; auto-disabled on localhost/dev/SSR |
workerUrl | string | — | Custom worker script URL |
workerConfig | object | — | Advanced worker tuning |
respectDoNotTrack defaults to true. When the visitor's browser sends a Do Not Track
signal, the Beacon collects and sends nothing. useWorker has no fixed default — it is
enabled on production-like hosts but automatically disabled on localhost, 127.0.0.1,
192.168.*, *.local, Next.js dev mode, and during SSR (when window is unavailable).
Beacon API
new CheckpointBeacon(config)
Creates a beacon and starts periodic collection. Throws if config.projectId is missing.
collect(eventType?)
Collect signals and send an event. eventType defaults to 'pageview'. Valid types: 'pageview', 'pageunload', 'heartbeat', 'custom', 'error', 'performance'.
await beacon.collect('pageview');trackEvent(name, metadata?)
Send a custom event with optional metadata. This is the correct way to attach your own data to a detection signal.
await beacon.trackEvent('button_click', {
buttonId: 'signup-cta',
page: '/home',
});trackPageUnload()
Convenience helper that records a pageunload event (uses a reliable unload send). Call it from a beforeunload/pagehide handler.
window.addEventListener('pagehide', () => beacon.trackPageUnload());updateConfig(updates)
Merge configuration at runtime. Changing useWorker/workerUrl/workerConfig restarts the worker; changing flushInterval restarts the collection loop.
beacon.updateConfig({ flushInterval: 10000 });destroy()
Stop automatic collection, terminate the worker, flush pending events, and release resources.
beacon.destroy();The Beacon is not an event emitter — there is no .on(...) API and no client-side detection
event. It also has no start(), stop(), identify(), or getDetectionResult() method.
Web Worker Mode
When useWorker is enabled and the browser is capable, the Beacon offloads processing to a Web Worker so the main thread stays free for UI rendering. Signals are collected on the main thread and handed to the worker, which handles batching and network requests.
import { CheckpointBeacon } from '@kya-os/checkpoint-beacon';
const beacon = new CheckpointBeacon({
projectId: 'acme-corp',
useWorker: true,
});Workers are automatically disabled in local development (localhost / Next.js dev) because dev
servers typically don't serve the worker file. The Beacon works the same without a worker. If a
bundler can't resolve the worker, set useWorker: false explicitly. If capabilities are
insufficient or a restrictive CSP blocks workers, the Beacon falls back to the main thread
automatically.
For advanced setups, the BeaconWorkerClient class is exported from @kya-os/checkpoint-beacon.
Data Collection
The Beacon gathers signals via two collectors that run automatically:
| Collector | Signals collected |
|---|---|
BrowserCollector | User agent, language, platform, screen & viewport, timezone, hardwareConcurrency, deviceMemory, touch points, referrer, vendor |
PerformanceCollector | Navigation timing, connection info, memory (Chrome), and paint timing (FCP/LCP) |
Browser Compatibility
Modern evergreen browsers are supported. Web Worker offloading requires a browser with the Worker API; otherwise the Beacon falls back to the main thread.
| Browser | Minimum Version |
|---|---|
| Chrome | 90+ |
| Firefox | 88+ |
| Safari | 14+ |
| Edge | 90+ |
Offline Support
The Beacon handles intermittent connectivity:
- When offline, events are queued in an
OfflineQueue(exported from the package) - The queue holds up to
maxQueueSizeevents - When connectivity returns, queued events are sent automatically
- Failed sends are retried with backoff (
retryAttempts/retryDelay)
Performance
The Beacon is designed to be lightweight: collection is non-blocking, events are batched, and work is offloaded to a Web Worker when available. For the current gzipped bundle size, see the npm package / bundlephobia badge rather than a hard-coded figure.
Network & CSP
The Beacon script loads from https://kya.vouched.id and POSTs detections to https://kya.vouched.id/v1/beacon. If your site restricts outbound requests (a Content Security Policy, a corporate proxy, or tracker blockers), allow that host.
For a CSP, add it to connect-src:
Content-Security-Policy: connect-src 'self' https://kya.vouched.id;Framework Integration
React
import { useEffect } from 'react';
import { CheckpointBeacon } from '@kya-os/checkpoint-beacon';
function App() {
useEffect(() => {
const beacon = new CheckpointBeacon({
projectId: process.env.NEXT_PUBLIC_CHECKPOINT_PROJECT_ID!,
});
beacon.collect('pageview');
return () => {
beacon.destroy();
};
}, []);
return <div>Your app</div>;
}Vue
import { onMounted, onUnmounted } from 'vue';
import { CheckpointBeacon } from '@kya-os/checkpoint-beacon';
let beacon: CheckpointBeacon;
onMounted(() => {
beacon = new CheckpointBeacon({
projectId: import.meta.env.VITE_CHECKPOINT_PROJECT_ID,
});
beacon.collect('pageview');
});
onUnmounted(() => {
beacon?.destroy();
});Troubleshooting
"Project ID is required for Checkpoint Beacon" thrown at construction
The constructor throws synchronously if config.projectId is missing or empty. Pass the same Project ID used elsewhere in your stack — see Credentials for where to find it.
No detections in the dashboard
- Check the Network tab for a
POSTtohttps://kya.vouched.id/v1/beacon. If it's absent, the Beacon isn't sending; if it's present but the response is not a2xx, the send is failing rather than the collection. - If the visitor's browser sends Do Not Track, the Beacon collects and sends nothing (
respectDoNotTrackdefaults totrue) — this is silent by design, not an error. - Confirm the Project ID matches the project you're viewing in the dashboard.
- A restrictive CSP, corporate proxy, or tracker blocker can block the request to
kya.vouched.id— see Network & CSP above.
Worker failures don't show up anywhere
Worker initialization and worker-send failures are caught internally, and the Beacon falls back to the main thread automatically — this is expected behavior, not a bug. To see what's happening, set debug: true and logLevel: 'debug', then look for [Checkpoint Beacon]-prefixed console messages such as "Worker unavailable, using main thread" or "Worker failed, using fallback".
Beacon vs Pixel
For a lightweight, no-code alternative, see the Marketing Pixel.
| Feature | Beacon | Pixel |
|---|---|---|
| Installation | npm package | Script tag / GTM |
| Signal richness | Higher | Basic |
| Custom events | trackEvent | Limited |
| Web Worker | Yes | No |
| Offline support | Yes | No |
| Best for | Applications | Marketing sites |
Next Steps
- Beacon Cookbook — Step-by-step beacon setup guide
- Marketing Pixel — Lightweight alternative for marketing sites
- Dashboard Analytics — View detection data
- Enforce — Add server-side enforcement
