What's calling your Fastify API? A developer demonstrated adding request detection to a small Fastify API using the WebDecoy Fastify plugin, registering it in monitor mode so a decoy /.env request produces a DENY tripwire verdict without altering the API's 404 response. The walkthrough covers local tripwire rules, an optional WebDecoy API key for cloud detections, and a labeled test request that verifies reporting while leaving the endpoint's catalog response intact. A product endpoint can serve your frontend, a customer integration, and a scraper through the same URL. Your page analytics may only show the first of those. Let's add request detection to a small Fastify API, watch a decoy request produce a verdict, and connect the results to a dashboard. We'll start in monitor mode so detection does not change the API's responses. We build WebDecoy. This example uses its Fastify plugin and was prepared with AI assistance. It is request monitoring, separate from our FCaptcha project. Use Node.js 22.12 or later. Create a folder and install the versions used here: mkdir fastify-crawler-monitor cd fastify-crawler-monitor npm init -y npm install --save-exact fastify@5.12.5 @webdecoy/fastify@0.18.3 @webdecoy/node@0.18.3 Create server.mjs : python import Fastify from 'fastify'; import webdecoy from '@webdecoy/fastify'; import { tripwire } from '@webdecoy/node'; const app = Fastify { logger: false, trustProxy: false } ; const apiKey = process.env.WEBDECOY API KEY || undefined; await app.register webdecoy, { apiKey, mode: 'monitor', honeytoken: false, skipPaths: '/health' , rules: tripwire { paths: '/.env' } , } ; app.addHook 'preHandler', async request = { const decision = request.webdecoyDecision; if decision return; console.log { route: request.routeOptions.url, conclusion: decision.conclusion, tripwire: decision.deniedBy 'tripwire' , dashboardConfigured: Boolean apiKey , } ; } ; app.get '/health', async = { ok: true } ; app.get '/api/products', async = { products: { id: 1, name: 'Field notebook' } , } ; app.get '/.env', async request, reply = reply.code 404 .send { error: 'Not found' } ; await app.listen { host: '127.0.0.1', port: 4310 } ; Register WebDecoy before your routes and the hook that reads its decision. Fastify's hook documentation https://fastify.dev/docs/latest/Reference/Hooks/ explains the lifecycle and scope. The /.env handler returns a fixed 404. It never opens an environment file. We use a path that should not contain public application content to demonstrate a concrete rule. Automatic HTML trap injection is disabled here with honeytoken: false . The sample is a JSON API, and we only need request observation for this walkthrough. Start the server: node server.mjs From another terminal: curl -i http://127.0.0.1:4310/api/products curl -i http://127.0.0.1:4310/.env The product request returns HTTP 200 and a small catalog. The decoy path returns HTTP 404, while the terminal records: route: '/.env' conclusion: 'DENY' tripwire: true dashboardConfigured: false DENY is the rule verdict. Monitor mode lets the application continue, so the response is still the 404 defined by the handler. Seeing a refusal in the log does not mean the request was blocked. The example only logs a route template and a few decision fields. It does not print request headers, query strings, bodies, IP addresses, or API keys. The local tripwire works without an account. To collect cloud detections, create a WebDecoy account https://app.webdecoy.com/?utm source=devto&utm medium=tutorial&utm campaign=fastify crawler monitor and create an API key for the site you want to observe. Put the key in a local .env file: WEBDECOY API KEY=your api key Add .env to .gitignore . Keep the key on the server, including when you deploy. It does not belong in the frontend that calls this API. Stop and restart the server with the file loaded: node --env-file=.env server.mjs Then send the reserved test request: curl -i -A 'WebDecoy-Test/1.0' http://127.0.0.1:4310/api/products Open Detections in WebDecoy and look for the labeled Test record. The endpoint should still return its catalog. The record verifies reporting; it does not represent an actual AI agent visiting your API. dashboardConfigured: true only means the process received a key. It does not prove the key is valid or the report arrived. If the record is missing, check the selected site, the key, outbound connectivity, and reporting errors. Keep your existing authentication, validation, authorization, and handlers. Add the plugin before the routes you want covered rather than replacing your server with this demo. The demo binds to loopback and trusts no proxy headers. For deployment, configure trusted proxies for your actual network using the SDK's proxy settings https://docs.webdecoy.com/sdk-plugins/node-sdk/ . Do not trust arbitrary forwarded IP headers. Otherwise the traffic you are reviewing can be attributed to the wrong client. This plugin uses preHandler . Requests rejected earlier in Fastify's lifecycle may never reach it. Likewise, requests served entirely by an upstream CDN do not reach this Node process. Use edge collection or access logs for those parts of the traffic. After deploying, look at the paths, repeated requests, classification, and supporting signals. A client using a familiar AI crawler name is not automatically that company's crawler. A legitimate customer integration is automated too, which is one reason to observe before enforcing a policy. Detections are not a complete request counter. Some requests can be allowed locally without cloud reporting, and caching can affect reporting frequency. Keep access logs for overall volume. Start with one useful question: which automated clients are repeatedly requesting the content this API exposes? Once you can answer that, you have a better basis for deciding what to allow, limit, or investigate. The example was tested locally on Node.js 26.5.0 with Fastify 5.12.5 and WebDecoy packages 0.18.3. Checks covered the catalog response, the tripwire verdict with its unchanged 404, the skipped health route, and the reserved test request in monitor mode. No API key was used in those checks. Cloud delivery and real-crawler identification are not claimed as local test results.