Checkpoint Docs
Concepts

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

SurfaceInstallDetection runsCan enforce?Unique signals
Pixel<script src="https://kya.vouched.id/pixel.js" data-project-id="…">Server-side, per eventNo (observe-only)Browser and device attributes, headless/automation probes, behavioural counters, visitor geo
Beacon@kya-os/checkpoint-beacon script or npm packageNot 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.AspNetCoreIn-process (WASM engine, no network call)Yes — permit, block, challenge, redirect, instruct, plus observe modeFull VerifyResult with trace[] and engineInfo, RFC 9421 and delegation verification
DNS gatewayCNAME your hostname to cname.checkpoint-gateway.aiIn the Cloudflare worker, before your originYes — allow, block, redirect, challenge, log, instructTLS/JA4 fingerprint, verification tier and signal strength in the recorded event, WWW-Authenticate: Delegation challenges
Direct APIPOST /api/v1/detect or /api/v1/enforce with an API keyServer-side, on the fields you send/enforce returns the decision; acting on it is your codeCaller-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:

  1. The trace id of an inbound traceparent header.
  2. An x-kya-correlation-id header.
  3. A correlationId (or pixel requestId) field in the request body.
  4. 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:

IdentifierWhere it appearsNotes
requestId/detect and /enforce responses, X-Request-ID headerCaller-supplied echo (or generated); not persisted as a column
eventId/pixel, /event, /batch responsesPixel-family only; /beacon and /log-detection return 202 with no id
sessionIdPixel and beacon events, echoed backThe 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_idPersisted on every surfaceThe 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:

SurfacePrecedence
Pixel / public API routesx-forwarded-for[0]x-real-ipcf-connecting-ip
DNS gatewaycf-connecting-ipx-forwarded-for[0]x-real-ip
Next.js middlewarex-forwarded-for[0]req.ip
Express middlewarex-forwarded-for[0]req.ip → socket address
.NET middlewareX-Forwarded-ForX-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, else cf-ipcountry
  • Region: x-vercel-ip-country-region, else cf-region
  • City: x-vercel-ip-city, else cf-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 NULL unless the request came through Cloudflare with cf-asn present.

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