Set up browser posture

Set up browser posture with the kya-os CLI, serve the beacon from your own domain, verify the signed token on your server, and check the result on a live page

Browser posture gives your server a signed copy of Checkpoint's verdict on a browser session, so a protected request can carry it. This page takes the CLI path to a verified token on a live page.

A verified token shows that Checkpoint issued it for this page and this session, and it carries Checkpoint's verdict. It doesn't identify the visitor. A server that finds no token, or a refused one, should treat the request as having no posture, never as permission.

Prerequisites

  • kya-os 2.3.1 or later, signed in with kya-os login. Posture calls use your management sign-in. Changing origins needs write access to the project.
  • @kya-os/checkpoint-beacon 1.4.3 or later on the pages. The CLI installs the release it verified. Below 1.4.3 it installs the beacon without posture wiring and prints a note; the reference has the version rules.
  • Pages served over HTTPS. The beacon doesn't store the token on other pages.
  • A server or edge you control that can read two request headers and verify an ES256 signature.

Add the beacon and set its origins

Every project has a posture key, so there is nothing to turn on. Add the beacon and set the page origins in one command:

kya-os setup --surface beacon --origins https://www.example.com,https://shop.example.com

kya-os detect install takes the same flags: --origins and --proxy. The flag tables list them and how the CLI normalizes each origin.

--origins replaces the project's additional origins, so an origin you leave out is removed. On setup, --origin (singular) is the Checkpoint address you sign in to. Page origins are --origins (plural).

Checkpoint issues a token only to pages on the project's domain and the origins you list. Wildcards, the apex rule, and the limit of 20 are in Verifying the posture token.

Without a terminal, the CLI asks nothing and leaves the origins as they are unless you pass --origins. Signed out, detect install notes that browser posture was not configured and carries on. The reference lists the terminal prompt and every other case.

The CLI then checks the key and prints key: confirmed at the keys endpoint or key: not confirmed, with a note that says why. --json output carries keyConfirmed. The reference has the retry rules. Tokens start flowing within 10 minutes, and Verifying the posture token explains the delay.

Check what the CLI wrote

Before it installs the beacon, the CLI checks the release against the npm registry and jsDelivr, and installs nothing if either check fails. The reference has the checks, the pinning rules, and the table by stack. For posture:

  • Self-hosted files. For an HTML page with a static directory, beacon.worker.js goes into _checkpoint/<version>/ at any beacon version. For a package install at beacon 1.4.3 and later, both beacon.worker.js and beacon-lazy-signals.min.js go into _checkpoint/<version>/ under your static directory (public/ for Next.js). The CLI sets workerUrl, and lazySignalsUrl where it wrote the chunk, to those paths, so a page with a strict Content-Security-Policy can load them from its own origin.
  • A header helper. The generated code exports checkpointPostureHeaders(), or window.checkpointPostureHeaders() in an HTML page. It returns KYA-Posture-Session always and KYA-Posture while the latest token hasn't expired.

Use the helper on requests to a protected API on another host, where the beacon's host-only cookies don't travel:

import { checkpointPostureHeaders } from './checkpoint-detect';

await fetch('https://api.example.com/generate', {
  method: 'POST',
  headers: { ...checkpointPostureHeaders() },
});

For a cross-origin request, add both header names to the API's Access-Control-Allow-Headers: KYA-Posture, KYA-Posture-Session. The CLI prints that line with the verifier configuration.

Serve the beacon through your own domain

A strict Content-Security-Policy may not allow connect-src https://kya.vouched.id or a script from a CDN. --proxy moves both onto your origin under a path you choose:

kya-os detect install --surface beacon --proxy /_cp

The beacon's endpoint becomes /_cp/api/v1/beacon, which your domain forwards to https://kya.vouched.id/api/v1/beacon. The forwarding uses Forward edge evidence: your edge sets X-API-Key and KYA-Forwarded-IP on the proxied request, and Checkpoint scores it with the visitor's address instead of your proxy's.

The proxy key has the proxy purpose, which carries only the forward permission: it authenticates forwarded evidence and nothing else. It's shown once, and minting needs a project owner or admin. Signed in, the CLI stores it as CHECKPOINT_PROXY_KEY in a gitignored env file and never prints it. Signed out, it tells you to sign in and re-run, or to set the variable yourself.

For Next.js, the CLI writes a helper, checkpoint-beacon-proxy.ts, and one entry file. For nginx, Cloudflare, and Akamai, it prints the configuration for you to add and applies nothing. Checkpoint hasn't verified the printed Akamai rule against a live property, so test it in staging. The reference lists each target.

Every generated proxy removes the visitor's Cookie and Authorization headers. The Next.js proxy and the Cloudflare Worker also remove every KYA-Forwarded-* header the visitor sent. The nginx block and the Akamai rule clear only three KYA-Forwarded-* headers by name (JA4, challenge, challenge-id), so delete any other KYA-Forwarded-* header yourself.

On Vercel and other hosts, .env.local isn't deployed. Set CHECKPOINT_PROXY_KEY in the host's environment too.

With the proxy in place, connect-src needs only 'self', and script-src needs only 'self' when the script is self-hosted. The CLI prints the exact directives for your install. The reference lists what each one needs, including when to add base-uri 'none' and trusted-types checkpoint-beacon.

Verify the token on your server

Your server reads the KYA-Posture and KYA-Posture-Session headers, or the matching cookies on the same host. Verifying the posture token has the precedence, cookie names, and token format.

The server SDKs verify for you. They need the project's UUID (the lowercase UUID, not the friendly id), the key, and the page origin. Get the key id and public JWK from any of these:

  • The Next.js, Express, or ASP.NET Core block the CLI prints on every setup and detect install run. The Java block carries the keys URL only, and no block prints without a usable page origin.
  • posture.state.keyId and posture.state.publicJwk in --json output.
  • The Installations card in step 6.

The examples read them from environment variables. For a server that answers for several origins, list the extras next to the primary:

middleware.ts
export default withCheckpoint({
  projectId: '<lowercase project UUID>',
  browserPosture: {
    origin: 'https://www.shop.example',
    origins: ['https://checkout.shop.example', 'https://*.shop.example'],
    keyId: process.env.CHECKPOINT_POSTURE_KEY_ID!,
    publicKey: JSON.parse(process.env.CHECKPOINT_POSTURE_PUBLIC_JWK!),
  },
});
SettingRule
originThe primary, and required. Java and .NET refuse one that isn't canonical: https, a lowercase host, no default port, and no path.
originsAdditional to origin, not a replacement, so a config without origins behaves as before. It takes the same entries the CLI accepts, up to 20.
WildcardsThe TypeScript verifier refuses wildcards over shared suffixes, as the CLI does. Java and .NET don't check, so list only hosts you own.
The keyYou supply it, pinned or (in Java) fetched from the keys URL. The verifiers never take it from the token.

SDK verifiers compare the token's signed origin claim with origin and origins, then check that aud names the same origin. They ignore the request's Host and Origin headers. The gateway needs no configuration: it matches the request's origin against the project's domain and additional origins, then checks the token against the match.

PackageMinimum version
@kya-os/checkpoint-shared (used by @kya-os/checkpoint-express and @kya-os/checkpoint-nextjs)1.5.2
Java ai.knowthat.checkpoint0.2.2
.NET KyaOs.Checkpoint1.11.9
@kya-os/checkpoint-wasm-runtime (Next.js and Express postureOutcome reporting)1.10.1

A refused token doesn't stop the request, grant an allow, or override a block. The request proceeds as if it carried no posture. The only trace is telemetry: the SDK sends postureOutcome with status of verified, absent, or rejected, and a reason on every rejected, to POST /api/v1/log-detection.

ReasonWhat it means
malformedNot a well-formed token, or its header is unusable.
unknown-keyThe token's kid isn't the key you configured.
bad-signatureThe signature doesn't verify against your key.
invalid-claimsA claim is missing or the wrong shape, the issuer is wrong, or the lifetime is over 120 seconds.
project-mismatchproject_id isn't the project you configured.
origin-mismatchThe token's origin is neither origin nor one of origins.
audience-mismatchaud doesn't name that origin.
expiredThe token's lifetime has ended.
not-yet-validiat or nbf is in the future.
binding-mismatchbinding isn't the SHA-256 of the session id you were sent.
session-missingA token arrived with no session id.

Read the outcome in your handler

In Java, .NET and Express the middleware or filter has already verified the token, so your handler can read the result instead of verifying it a second time. In Next.js the accessor verifies the token itself. Every runtime gives you the same three statuses (verified with the verified claims, absent, or rejected with a reason). verified means the token's signature and claims are valid, not that the visitor is human: check the verdict in the claims. In the Java, .NET and Express middleware a missing result means the request was not checked, which is not the same as absent: treat it as unknown before you gate an action on it. The Next.js accessor verifies the token itself, so it always returns a status.

RuntimeHow to read it
Next.jsawait getCheckpointPostureOutcome(request, { browserPosture, projectId }) from @kya-os/checkpoint-nextjs. It verifies the token itself, so it always returns a status. See Next.js.
ExpressgetCheckpointPostureOutcome(req) from @kya-os/checkpoint-express. See Express.
JavaThe request attribute PostureOutcome.REQUEST_ATTRIBUTE (ai.knowthat.checkpoint.postureOutcome) with CheckpointServletFilter, or CheckpointFilter.postureOutcome(exchange) with CheckpointFilter.
ASP.NET Core and .NET FrameworkHttpContext.GetCheckpointPostureOutcome(). See .NET.
PostureOutcome posture = (PostureOutcome) request.getAttribute(PostureOutcome.REQUEST_ATTRIBUTE);
if (posture != null && posture.status() == PostureOutcome.Status.VERIFIED) {
    BrowserPosture claims = posture.posture().orElseThrow();
    // check claims.verdict() before treating the visitor as human
}

Check the page with detect doctor

kya-os detect doctor fetches a live page and reports whether the beacon and its posture path can work there:

kya-os detect doctor --url https://www.example.com

It reads the page's HTML, then probes the files and headers the beacon needs. Each check reports ok, info, warn, or fail, and the command exits 1 if any check fails. The reference lists every check and the scan limits. These catch most posture problems:

CheckWhat it confirms
Beacon filesThe script, the lazy-signals chunk, and the worker each answer 200 and are served as JavaScript, and the script's bytes match the integrity hash on its tag.
Content-Security-Policyscript-src, worker-src, and connect-src allow what the page loads and the beacon's endpoint. A page that requires Trusted Types must allow the checkpoint-beacon policy that beacon 1.4.3 and later create.
Proxy preflightA proxied endpoint on another origin answers a browser's OPTIONS request.
Posture, when signed inThe page's origin is allowed to carry posture, the keys endpoint serves the project's key, and the page's beacon uses the project you compared.

A failing run:

Checked https://staging.example.com/
  [ok  ] page: https://staging.example.com/ answered 200
  [ok  ] beacon script integrity: the served bytes match the page's integrity hash
  [FAIL] posture origin: https://staging.example.com is not among the allowed origins (https://www.example.com, https://shop.example.com)
         fix: Run: kya-os detect install --surface beacon --project <project uuid> --origins 'https://shop.example.com,https://staging.example.com' (--origins replaces the whole list, so this keeps the current additional origins)
1 problem(s) found.

The doctor doesn't check that your page forwards KYA-Posture headers to your API or that the API allows them in CORS. Test that with a real request.

Watch it in Installations

Open Installations in the project's dashboard, at /dashboard/<org>/<project>/installations. The Browser posture card shows:

PartWhat it shows
Key id, Primary origin, Keys URLThe primary origin comes from the project's Site URL, and the card says to set one if it's empty. The key id and keys URL have copy buttons.
Additional site originsThe same list --origins sets, with the same validation and a limit of 20. Save origins saves without touching the rest of the project.
Public keyThe JWK for your server's configuration, with a copy button.
Posture healthThe last 24 hours of beacon responses, by status and page origin.
Server-side verificationVerified, No token sent, and Rejected counts, with the reason for each rejection, once your servers report postureOutcome.

Anyone who can read the project sees the health view. Editing origins needs write access.

Posture health shows the share of responses issued a token, then a row per status and origin: Issued, Origin not allowed, Site URL not set, Signer unavailable, or State unavailable.

Counts are approximate: Checkpoint counts responses as they happen, not from stored detections, and buffers repeats briefly, so a burst can undercount. Each row's origin is what the browser reported, and it isn't verified. Each hour keeps 50 named origins per status and sums the rest as other origins, not listed. A flood of made-up origins can't hide your totals, though it can push a rarely seen real origin into that row. An origin's first occurrence in each short window is written straight away, so your own first page load shows up at once.

Common problems

Next steps

A token can also carry the beacon's own agent or bot verdict in an optional detection claim, which the server SDKs use to reclassify a request the server had called human. Verifying the posture token has the token's claims, for a stack the SDKs don't cover. Forward edge evidence covers the proxy headers and key checks. The CLI reference lists every flag, file, and JSON field.