Skip to main content
Ask your AI

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.console

You should see:

success: true
  Duration: 42ms

What 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

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)