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-nextjsGet 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_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxNever 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
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
- Visit your Vouched KYA dashboard
- Select your project
- Go to Analytics
- 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
Callback Never Fires
- Next.js:
onAgentDetectedonly runs when a request is classified as an agent. Normal browser traffic won't trigger it. - Express:
onResultfires on every request with the full verdict; checkresult.detectionDetail.detectedAgentto 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) andonResult(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: