Checkpoint Docs
IntegrationsExpress

Express Integration

Server-side AI agent detection for Express and Node.js applications

Overview

The Checkpoint Express integration provides server-side AI agent detection and enforcement for Node.js applications. Mount one middleware factory — withCheckpoint — and every request flows through the in-process WASM engine: UA pattern detection, KYA-OS signature verification, scope evaluation, delegation-chain verification, and policy evaluation.

  • < 2ms latency — In-process WASM engine, no per-request network hop
  • Enforce or Observe modes — Active enforcement or log-only dry runs
  • Cedar policy enforcement — Enforces your deployed dashboard policy in-process, byte-for-byte the same as the DNS Gateway
  • Route-level control — Protect specific endpoints
  • Composable observability — Session tracking and event storage via onResult
  • TypeScript — Full type definitions included

Prerequisites

  • A Checkpoint project — get your Project ID and API key from Installations in the dashboard.
  • An existing Express app. withCheckpoint needs express.json() mounted ahead of it (see Quick Setup below) so it can parse the KYA-OS envelope.

Installation

npm install @kya-os/checkpoint-express

Quick Setup

const express = require('express');
const { withCheckpoint } = require('@kya-os/checkpoint-express');

const app = express();
app.use(express.json()); // body-parser — required for KYA-OS envelope parsing

app.use(
  withCheckpoint({
    tenantHost: 'your.tenant.example',
    apiKey: process.env.CHECKPOINT_API_KEY, // enables dashboard reporting
  })
);

app.get('/', (req, res) => {
  res.send('Protected by Checkpoint');
});

app.listen(3000);
import express from 'express';
import { withCheckpoint, type CheckpointConfig } from '@kya-os/checkpoint-express';

const app = express();
app.use(express.json()); // body-parser — required for KYA-OS envelope parsing

const config: CheckpointConfig = {
  tenantHost: 'your.tenant.example',
  apiKey: process.env.CHECKPOINT_API_KEY, // enables dashboard reporting
  projectId: process.env.CHECKPOINT_PROJECT_ID, // enforces your deployed Cedar policy
};

app.use(withCheckpoint(config));

app.listen(3000);

The legacy createEnhancedAgentShieldMiddleware was removed in @kya-os/checkpoint-express@1.3.0. Session tracking and event storage are now composable primitives wired through withCheckpoint — see Express Observability & Storage.

Configuration

The most common withCheckpoint options:

withCheckpoint({
  // Required
  tenantHost: string,              // Tenant identifier (your dashboard hostname)

  // Dashboard integration
  apiKey?: string,                 // Project API key — required for dashboard reporting
  projectId?: string,              // Enforce this project's deployed Cedar policy in-process
  baseUrl?: string,                // Dashboard base URL (default: https://kya.vouched.id)

  // Behavior
  enforcementMode?: 'enforce' | 'observe',  // Default: 'enforce'
  policyCacheTtlSeconds?: number,  // Policy-fetch cache TTL (default: 300)

  // Observability
  onResult?: (result, req) => void, // Fires after every verification with the full VerifyResult
  debug?: boolean,                  // Surface reporter + policy telemetry via console.warn
});

See Middleware Enforcement for the complete option table, response headers, and response shapes shared with the Next.js and .NET middleware.

Environment Variables

# .env
CHECKPOINT_API_KEY=your_api_key_here
CHECKPOINT_PROJECT_ID=your_project_id_here

apiKey is required for detections to land in the dashboard — without it the verdict path works locally, but the onboarding Verify connection check never passes because no detection rows are written. Get both values from Installations in the dashboard.

Enforce vs Observe Mode

ModeBehavior
enforceApplies the policy verdict — block, redirect, or challenge for consent/identity. Default.
observeClassifies every request and stamps X-Checkpoint-Would-Have-Been headers. All traffic passes through.
// Observe mode — log everything, block nothing
app.use(
  withCheckpoint({
    tenantHost: 'your.tenant.example',
    apiKey: process.env.CHECKPOINT_API_KEY,
    enforcementMode: 'observe',
  })
);

Start in observe, review the would-have-been decisions in the dashboard, then remove the override (or set 'enforce') when the verdicts look right.

Response Headers

The engine sets X-Checkpoint-* response headers and a __checkpoint_verdict cookie — byte-identical to the Next.js sibling package. In observe mode, X-Checkpoint-Would-Have-Been is added instead of enforcing the verdict.

When session tracking is wired via withSessionTracking, ExpressSessionTracker additionally emits kya-session, kya-session-agent, and kya-session-id. See Express Observability & Storage for session tracking setup, and Middleware Enforcement for the complete header reference shared with Next.js and .NET.

Observability & Storage

Plain withCheckpoint attaches nothing to req — read verdicts from the onResult callback, which fires after every verification (errors thrown inside it are swallowed so observability can never break the verdict path):

app.use(
  withCheckpoint({
    tenantHost: 'your.tenant.example',
    apiKey: process.env.CHECKPOINT_API_KEY,
    onResult: (result, req) => {
      console.log(req.path, result.detectionDetail.detectionClass.type);
    },
  })
);

For session continuity (withSessionTracking, req.checkpoint) and persisting detection events to Memory/Redis/custom stores (createStorageAdapter), see Express Observability & Storage.

Usage Patterns

Global Protection

// Protect all routes
app.use(
  withCheckpoint({
    tenantHost: 'your.tenant.example',
    apiKey: process.env.CHECKPOINT_API_KEY,
  })
);

app.get('/api/data', (req, res) => {
  res.json({ protected: true });
});

Route-Specific Protection

withCheckpoint returns standard Express middleware, so you can mount it per-route. Construct it once and reuse it:

const protect = withCheckpoint({
  tenantHost: 'your.tenant.example',
  apiKey: process.env.CHECKPOINT_API_KEY,
});

// Only protect specific routes
app.get('/api/sensitive', protect, (req, res) => {
  res.json({ sensitive: 'data' });
});

// Unprotected route
app.get('/api/public', (req, res) => {
  res.json({ public: 'data' });
});

Testing

const request = require('supertest');
const app = require('./app');

describe('Checkpoint Protection', () => {
  it('should allow normal browsers', async () => {
    const response = await request(app)
      .get('/api/protected')
      .set('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)...');

    expect(response.status).toBe(200);
  });

  it('should pass agents through in observe mode with a would-have-been header', async () => {
    const response = await request(app).get('/api/protected').set('User-Agent', 'GPTBot/1.0');

    // observe mode: request passes through, verdict is stamped on the response
    expect(response.status).toBe(200);
    expect(response.headers['x-checkpoint-would-have-been']).toBeDefined();
  });
});

Troubleshooting

Middleware not working

  • Verify tenantHost is set — it is the one required option
  • Ensure app.use(express.json()) runs before withCheckpoint (required for KYA-OS envelope parsing)
  • Ensure the middleware is mounted before your routes (app.use before app.get)
  • Enable debug: true to surface reporter and policy telemetry

Detections not showing in the dashboard

  • Set apiKey — without it, verdicts are computed locally but never reported
  • Enable debug: true and watch for reporter warnings to confirm apiKey / baseUrl routing

Policy changes not taking effect

  • Fetched policies are cached for policyCacheTtlSeconds (default 300s) — wait out the TTL or lower it
  • Confirm the policy is deployed (not a draft) and engine enforcement is enabled for the project

Next Steps