Cookbooks

Detect: Middleware Detection

Add server-side AI agent detection to Next.js or Express applications

Goal

Add server-side AI agent detection to your Next.js or Express application using Vouched KYA middleware. By the end of this cookbook, you'll have:

  • Server-side detection running on every request
  • Detection data flowing to your dashboard
  • A server-side callback to react to detected agents
  • A foundation ready for enforcement (blocking) when you're ready

Best for: Applications that need server-side detection without blocking, or as a stepping stone to enforcement.

Next.js and Express use different deployment shapes. The Next.js cookbook below uses withCheckpointApi (the SaaS-gateway shape: no local WASM, centralized dashboard policy). The Express cookbook uses withCheckpoint (the local-engine shape: Vouched KYA's detection engine runs in-process via WASM). Both report detections to the same dashboard.

Prerequisites

  • A Vouched KYA account with a project created
  • A Next.js 13+ or Express 4+ application
  • Node.js 20.19+ or 22.12+

Time Estimate

20 minutes


Steps

Install the Middleware Package

npm install @kya-os/checkpoint-nextjs

Get Your API Key

Find your project's API key on Installations: see Credentials for the exact click-path and key format.

Add it to your environment:

# .env.local
CHECKPOINT_API_KEY=sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Never commit API keys to source control. Use environment variables or a secrets manager. The API key is what makes detections land in your dashboard.

Add the Middleware

For current NextRequest objects (Next.js 15+), client-address evidence requires Vercel's managed runtime; self-hosted proxy and Cloudflare profiles return no address. Express behind a proxy requires an explicit proxy profile and a peer allowlist. Its default policy withholds a socket that Express regards as a proxy. See client-IP deployment guidance.

Create or update middleware.ts in your project root:

Next.js 16: middleware.ts → proxy.ts

In Next.js 16 this file convention was renamed. Name the file proxy.ts and export a proxy function (a default export also works); middleware.ts exporting middleware still works but is deprecated. The Vouched KYA setup below is the same either way, except that withCheckpoint on the Edge runtime also takes engineWasmModule. One caveat: proxy.ts runs on the Node.js runtime only, so if you want Vouched KYA on the Edge runtime (lowest latency), keep the file as middleware.ts. On Next.js 15 and earlier, use middleware.ts.
// middleware.ts
import { withCheckpointApi } from '@kya-os/checkpoint-nextjs/api-middleware';

export default withCheckpointApi({
  // Managed Vercel only; current self-hosted Next has no authenticated client-IP source.
  clientIpPolicy: { platform: 'vercel' },
  apiKey: process.env.CHECKPOINT_API_KEY!, // optional: falls back to CHECKPOINT_API_KEY
});

export const config = {
  matcher: [
    // Match all paths except static assets
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
};

This runs detection on every matched request and logs to your dashboard.

Enforcement is decided by your dashboard policy, not by the SDK. The middleware always calls the enforce API; whether a detected agent is blocked depends on your project's policy. To run detection-only, keep your dashboard policy in monitor/log mode. Omitting onBlock does not by itself disable blocking. onBlock only chooses how to act ('block' vs 'redirect') when the policy decides to block.

React to Detections

Use the onAgentDetected(request, decision) callback to run server-side logic when an agent is detected. It fires only when the request is classified as an agent, and it's observability-only: throwing here never changes the enforcement outcome.

// middleware.ts
import { withCheckpointApi } from '@kya-os/checkpoint-nextjs/api-middleware';

export default withCheckpointApi({
  // Managed Vercel only; current self-hosted Next has no authenticated client-IP source.
  clientIpPolicy: { platform: 'vercel' },
  apiKey: process.env.CHECKPOINT_API_KEY!,
  onAgentDetected: async (request, decision) => {
    console.log(
      JSON.stringify({
        event: 'agent_detected',
        agent: decision.agentName, // e.g. 'ChatGPT' (may be undefined)
        agentType: decision.agentType,
        confidence: decision.confidence, // 0–100
        action: decision.action, // policy decision: 'allow' | 'block' | 'redirect' | …
        path: request.nextUrl.pathname,
        timestamp: new Date().toISOString(),
      })
    );

    // Send to your analytics/monitoring
    // await logToDatadog(decision);
    // await sendToSlack(decision);
  },
});

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'],
};

This matcher also excludes common image extensions served outside next/image: see Route Matching for why that exclusion matters.

The decision object (EnforcementDecision) exposes action, isAgent, confidence, agentName, agentType, and reason: there is no detectionClass field on it. On an allowed request where an agent was detected, the middleware also sets response headers KYA-Detected, KYA-Confidence, and KYA-Agent (visible to the client). These are response headers: the middleware does not inject detection data as request headers, so route handlers can't read a kya-class/kya-confidence request header. React to detections in onAgentDetected instead.

Test the Integration

Send requests with different User-Agents and watch your logs / dashboard:

# Normal browser: not flagged as an agent
curl http://localhost:3000/

# AI agent: ChatGPT-User
curl -H "User-Agent: Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot" \
  http://localhost:3000/

# Traditional bot: Googlebot
curl -H "User-Agent: Googlebot/2.1" \
  http://localhost:3000/

The agent/bot requests should trigger your onAgentDetected / onResult log and appear in the dashboard. The authoritative classification (human, ai_agent, bot, incomplete_data) is shown in the dashboard Analytics tab.


Verify It's Working

Dashboard Check

  1. Visit your Vouched KYA dashboard
  2. Select your project
  3. Go to Analytics
  4. You should see detections from your test requests

Log Check

If you added a callback, check your console:

{
  "event": "agent_detected",
  "agent": "ChatGPT",
  "confidence": 85,
  "action": "allow",
  "path": "/",
  "timestamp": "2026-01-15T10:30:00.000Z"
}

Troubleshooting

No Detections in the Dashboard

SymptomCauseFix
Nothing in dashboardMissing/invalid API keySet CHECKPOINT_API_KEY (starts with sk_); recopy it
Middleware not hitMatcher / mount not matchingCheck config.matcher (Next.js) or app.use(...) (Express)

Callback Never Fires

  • Next.js: onAgentDetected only runs when a request is classified as an agent. Normal browser traffic won't trigger it.
  • Express: onResult fires on every request with the full verdict; check result.detectionDetail.detectedAgent to distinguish agents.

req.checkpoint is undefined (Express)

withCheckpoint does not populate req, and withSessionTracking (which used to set req.checkpoint) was removed. Keep the verdict on res.locals from onResult: see In an Express route handler.


What You Learned

  • How to add server-side detection to Next.js (withCheckpointApi) and Express (withCheckpoint)
  • How to react to detections via onAgentDetected (Next.js) and onResult (Express)
  • That Next.js enforcement is dashboard-policy-driven, and Express detection-only is enforcementMode: 'observe'
  • How to test with different user agents and confirm classifications in the dashboard

Next Steps

Ready to start blocking detected agents? See:

GoalNext Cookbook
Block AI agentsMiddleware Enforcement
DNS-level blockingGateway Setup
Configure policiesPolicy Configuration