Installation Surfaces
What each install option can see and enforce, how to combine them, and how to join their events
Checkpoint installs in five ways, and they are not interchangeable: each surface sees a different slice of the request, and only some can act on it. This page compares them, shows the recommended combinations, and documents the two operational details every integration eventually asks about — how to join events across surfaces, and where client IP and geo really come from.
The five surfaces at a glance
| Surface | Install | Detection runs | Can enforce? | Unique signals |
|---|---|---|---|---|
| Pixel | <script src="https://kya.vouched.id/pixel.js" data-project-id="…"> | Server-side, per event | No (observe-only) | Browser and device attributes, headless/automation probes, behavioural counters, visitor geo |
| Beacon | @kya-os/checkpoint-beacon script or npm package | Not classified (linkage data only) | No (observe-only) | Device entropy from the versioned signal registry, visitor geo |
| SDK middleware | @kya-os/checkpoint-nextjs, @kya-os/checkpoint-express, Checkpoint.AspNetCore | In-process (WASM engine, no network call) | Yes — permit, block, challenge, redirect, instruct, plus observe mode | Full VerifyResult with trace[] and engineInfo, RFC 9421 and delegation verification |
| DNS gateway | CNAME your hostname to cname.checkpoint-gateway.ai | In the Cloudflare worker, before your origin | Yes — allow, block, redirect, challenge, log, instruct | TLS/JA4 fingerprint, verification tier and signal strength in the recorded event, WWW-Authenticate: Delegation challenges |
| Direct API | POST /api/v1/detect or /api/v1/enforce with an API key | Server-side, on the fields you send | /enforce returns the decision; acting on it is your code | Caller-supplied requestId echo, get_policy |
Three facts worth internalizing before choosing:
- Only the pixel and beacon see the browser. Screen, timezone, languages, cookies, headless and automation probes, and behavioural counters exist only on client-originated events.
- Only the DNS gateway sees the TLS handshake. JA4 and TLS version are read from Cloudflare's connection metadata; no SDK or API call carries them.
- SDKs see headers only, but see them all. The middleware reads the full header map (signatures, delegation credentials, user agent) and runs the complete engine pipeline in-process, so it is the strongest enforcement point for agent identity — it just knows nothing about the device.
Recommended combination
For a web property, pair the pixel (device and behavioural signal, visitor geo) with either the SDK middleware or the DNS gateway (enforcement and cryptographic verification), and join their events with a correlation id as described below. Neither client surface can enforce on its own, and neither server surface can see the device on its own.
Joining events across surfaces
Every persisted detection carries a 32-hex correlation_id, resolved in this order:
- The trace id of an inbound
traceparentheader. - An
x-kya-correlation-idheader. - A
correlationId(or pixelrequestId) field in the request body. - A freshly minted id when none of the above is present.
That makes traceparent the one identifier you can carry from the browser, through your own backend, into Checkpoint, and back out to your logs.
The recipe:
// 1. Browser: send the same trace context to your backend and to the pixel.
const traceparent = `00-${traceId}-${spanId}-01`;
fetch('/api/checkout', { headers: { traceparent } });
// The pixel accepts a correlationId field on its events; pass the same traceId.
// 2. Your backend: forward the header when you call /enforce.
await fetch('https://kya.vouched.id/api/v1/enforce', {
method: 'POST',
headers: { 'x-api-key': process.env.CHECKPOINT_API_KEY, traceparent },
body: JSON.stringify({ userAgent, ipAddress, path, method }),
});Both events now persist with the same correlation_id, and the DNS gateway echoes it back to the client as KYA-Request-Id.
What the other identifiers are for:
| Identifier | Where it appears | Notes |
|---|---|---|
requestId | /detect and /enforce responses, X-Request-ID header | Caller-supplied echo (or generated); not persisted as a column |
eventId | /pixel, /event, /batch responses | Pixel-family only; /beacon and /log-detection return 202 with no id |
sessionId | Pixel and beacon events, echoed back | The pixel and beacon mint different session ids (sessionStorage vs a first-party cookie) and nothing propagates between them — do not join on sessionId across those two |
correlation_id | Persisted on every surface | The cross-surface join key; never echoed in a response body except as the gateway's KYA-Request-Id |
sessionId is not a join key
A beacon session and a pixel session for the same visitor have unrelated ids. If you need beacon
entropy tied to pixel detections, send the same traceparent alongside both, or scope
your analysis to correlation_id.
Client IP: what each surface reads
There is no single IP-resolution order today; each surface documents its own:
| Surface | Precedence |
|---|---|
| Pixel / public API routes | x-forwarded-for[0] → x-real-ip → cf-connecting-ip |
| DNS gateway | cf-connecting-ip → x-forwarded-for[0] → x-real-ip |
| Next.js middleware | x-forwarded-for[0] → req.ip |
| Express middleware | x-forwarded-for[0] → req.ip → socket address |
| .NET middleware | X-Forwarded-For → X-Real-IP → connection remote address (with port and chain normalization) |
On the server-to-server routes (/detect, /enforce, /log-detection) the IP that matters is the one you pass in ipAddress — always send the end client's address explicitly rather than relying on connection headers, which describe your server, not your visitor.
Geo and ASN: sourced from edge headers, not a GeoIP database
Checkpoint does not run a GeoIP lookup. Geo columns are filled from the edge headers of whatever request reached us:
- Country:
x-vercel-ip-country, elsecf-ipcountry - Region:
x-vercel-ip-country-region, elsecf-region - City:
x-vercel-ip-city, elsecf-ipcity - ASN / AS organization:
cf-asn/cf-asorganization(present only when the request traverses a Cloudflare zone with those headers enabled)
The consequences, per surface:
- Pixel and beacon events carry correct visitor geo, because the visitor's own browser made the request.
- Server-to-server calls carry your server's geo.
When your backend calls
/enforce, the edge headers describe your data-center PoP, not the end user — treat geo on those rows as absent. - DNS-gateway detections carry no geo at all today; the worker's recorded event does not include geo fields.
- ASN is best-effort.
Rows served by Vercel infrastructure have no ASN source; expect
NULLunless the request came through Cloudflare withcf-asnpresent.
No proxy or VPN classification
Checkpoint does not currently classify proxies, VPNs, datacenter egress, or anonymizers on any
surface. The IP intelligence that does exist is vendor attribution: whether the address falls
inside an AI vendor's published ranges (the Tier-2 vendor_ip_feed signal).
Where to go next
- Pixel setup and Beacon setup for the client surfaces.
- Middleware and Gateway for the enforcement surfaces.
- API Reference for the
/detectand/enforcecontracts.
