Bot detection
This server flow scores every incoming event for bot and AI-agent traffic before it reaches your destinations. The Express source forwards the request headers, the bot transformer reads them, and a demo destination logs the annotated events. Nothing is dropped: scoring is annotation, and you filter downstream.
{
"version": 4,
"flows": {
"default": {
"config": {
"platform": "server",
"bundle": {
"packages": {
"@walkeros/collector": {},
"@walkeros/server-source-express": {},
"@walkeros/server-transformer-bot": {},
"@walkeros/destination-demo": {}
}
}
},
"sources": {
"express": {
"package": "@walkeros/server-source-express",
"config": {
"settings": { "port": 8080 },
"ingest": {
"map": {
"userAgent": { "key": "headers.user-agent" },
"acceptLanguage": { "key": "headers.accept-language" },
"acceptEncoding": { "key": "headers.accept-encoding" },
"secFetchSite": { "key": "headers.sec-fetch-site" },
"secFetchMode": { "key": "headers.sec-fetch-mode" },
"secFetchDest": { "key": "headers.sec-fetch-dest" },
"secChUa": { "key": "headers.sec-ch-ua" },
"secChUaMobile": { "key": "headers.sec-ch-ua-mobile" },
"secChUaPlatform": { "key": "headers.sec-ch-ua-platform" },
"accept": { "key": "headers.accept" },
"method": { "key": "method" }
}
}
}
}
},
"transformers": {
"bot": {
"package": "@walkeros/server-transformer-bot",
"config": {
"settings": {
"context": "fetch",
"input": {
"acceptLanguage": "ingest.acceptLanguage",
"acceptEncoding": "ingest.acceptEncoding",
"secFetchSite": "ingest.secFetchSite",
"secFetchMode": "ingest.secFetchMode",
"secFetchDest": "ingest.secFetchDest",
"secChUa": "ingest.secChUa"
}
}
}
}
},
"destinations": {
"console": {
"package": "@walkeros/destination-demo",
"config": {
"settings": { "name": "Event Logger" }
}
}
},
"collector": { "run": true }
}
}
}Run it
Save the config as flow.json and push an event through the built flow with the destination mocked. Nothing is sent anywhere:
npx walkeros push flow.json -e '{"name":"page view"}' --simulate destination.consoleYou should see:
success: true
Duration: 42msWhat you get
Every event now carries user.botScore (0-99, higher means more automated), user.botCategory (what kind of client it is) and user.botProduct when a named detector matched. The reason codes land on ingest.bot.reasons, off the analytics event. In production the demo destination logs each annotated event; a request from GPTBot looks like this (from the transformer's examples):
[demo] {
"name": "page view",
"user": {
"botScore": 90,
"botCategory": "ai-crawler",
"botProduct": "GPTBot"
},
...
}Offline runs have no request behind them, so simulated events resolve no signals at all and score null with category unknown, which means not measured rather than human. ingest.bot.reasons then names which mappings are missing. To act on the scores, add a mapping condition on your real destinations that drops or segments events where botScore exceeds 50. See Bot detection for the full filtering recipes.
Next steps
- Bot detection transformer: Score ladder, categories, and filtering recipes
- Bundled mode: Build and run the flow with the CLI