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 Checkpoint 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 — the Rust kya-os-engine runs
in-process via WASM). Both report detections to the same dashboard.
Prerequisites
- A Checkpoint account with a project created
- A Next.js 13+ or Express 4+ application
- Node.js 18+
Time Estimate
20 minutes
Steps
Install the Middleware Package
npm install @kya-os/checkpoint-nextjsnpm install @kya-os/checkpoint-expressGet 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
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 Checkpoint setup below is identical either way — only the file name and export name change. One caveat: proxy.ts runs on the Node.js runtime only, so if you want Checkpoint 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({
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.
Add the middleware to your Express app. Use enforcementMode: 'observe' for detection-only (nothing is blocked — requests pass through):
// app.ts or server.ts
import express from 'express';
import { withCheckpoint } from '@kya-os/checkpoint-express';
const app = express();
app.use(
withCheckpoint({
tenantHost: 'acme.checkpoint.example', // your tenant hostname from the dashboard
apiKey: process.env.CHECKPOINT_API_KEY!, // reports detections to the dashboard
enforcementMode: 'observe', // detection-only — pass everything through
onResult: (result, req) => {
const d = result.detectionDetail;
if (d.detectedAgent) {
console.log(`Detected ${d.detectedAgent.name} (${d.confidence}%) on ${req.path}`);
}
},
})
);
app.get('/', (req, res) => {
res.json({ message: 'Hello!' });
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});tenantHost is required. In observe mode the middleware never blocks — it adds
X-Checkpoint-Would-Have-Been headers so you can see what enforcement would have done. Switch
to enforcementMode: 'enforce' (the default) when you're ready 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({
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.
withCheckpoint does not attach anything to req by default — read the verdict in onResult(result, req) (shown above). If you want per-request access on req inside your route handlers, wire the optional session tracker:
// app.ts
import express from 'express';
import cookieParser from 'cookie-parser';
import { withCheckpoint, withSessionTracking } from '@kya-os/checkpoint-express';
import type { AgentShieldRequest } from '@kya-os/checkpoint-express';
const app = express();
app.use(cookieParser());
app.use(
withSessionTracking(
withCheckpoint({
tenantHost: 'acme.checkpoint.example',
apiKey: process.env.CHECKPOINT_API_KEY!,
enforcementMode: 'observe',
}),
{ enabled: true }
)
);
app.get('/data', (req, res) => {
const detection = (req as AgentShieldRequest).agentShield;
// Populated for a tracked agent session (nested under `.result` / `.session`)
if (detection?.result?.detectedAgent) {
console.log(`Agent ${detection.result.detectedAgent.name} accessing /data`);
}
res.json({
data: 'your data here',
agent: detection?.session?.agent ?? null,
confidence: detection?.session?.confidence ?? null,
});
});
export default app;req.agentShield only populates once you wire withSessionTracking (and mount cookie-parser) —
see Session tracking for the full requirement and the
nested shape.
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 — GPTBot
curl -H "User-Agent: Mozilla/5.0 (compatible; GPTBot/1.0; +https://openai.com/gptbot)" \
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 Checkpoint 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
| Symptom | Cause | Fix |
|---|---|---|
| Nothing in dashboard | Missing/invalid API key | Set CHECKPOINT_API_KEY (starts with sk_); recopy it |
| Middleware not hit | Matcher / mount not matching | Check config.matcher (Next.js) or app.use(...) (Express) |
| Express: startup err | tenantHost missing | tenantHost is required for withCheckpoint |
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.agentShield is undefined (Express)
Plain withCheckpoint does not populate req — see Session tracking for the withSessionTracking + cookie-parser wiring this needs.
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:
| Goal | Next Cookbook |
|---|---|
| Block AI agents | Middleware Enforcement |
| DNS-level blocking | Gateway Setup |
| Configure policies | Policy Configuration |