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
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:
sessionId joins browser surfaces only
Pixel, beacon, and gateway rows for the same browser share the _as_beacon_session
cookie's value, cookie consent permitting. Server-to-server /detect and
/enforce calls carry no such cookie, so to tie those to browser rows send the same
traceparent alongside both, or scope your analysis to correlation_id.
Client IP: what each surface reads
IP resolution is selected at the ingress boundary. The same header can be trusted in one deployment and observational in another, so the platform profile—not header presence—determines the result:
x-real-ip and non-authenticated forwarding headers are observational
fallbacks. They must not be used for rate-limit identity or other security
decisions unless the selected deployment profile authenticates them. SDKs
default to direct; configure the profile above explicitly for your
deployment, and for the trusted-proxy profile keep the Express or .NET
hop count aligned with the number of proxies your own infrastructure
controls and authenticates.
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.
