{"slug": "what-s-calling-your-fastify-api", "title": "What's calling your Fastify API?", "summary": "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.", "body_md": "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.\n\nLet'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.\n\nWe build WebDecoy. This example uses its Fastify plugin and was prepared with AI assistance. It is request monitoring, separate from our FCaptcha project.\n\nUse Node.js 22.12 or later. Create a folder and install the versions used here:\n\n```\nmkdir fastify-crawler-monitor\ncd fastify-crawler-monitor\nnpm init -y\nnpm install --save-exact fastify@5.12.5 @webdecoy/fastify@0.18.3 @webdecoy/node@0.18.3\n```\n\nCreate `server.mjs`:\n\n``` python\nimport Fastify from 'fastify';\nimport webdecoy from '@webdecoy/fastify';\nimport { tripwire } from '@webdecoy/node';\n\nconst app = Fastify({ logger: false, trustProxy: false });\nconst apiKey = process.env.WEBDECOY_API_KEY || undefined;\n\nawait app.register(webdecoy, {\n  apiKey,\n  mode: 'monitor',\n  honeytoken: false,\n  skipPaths: ['/health'],\n  rules: [tripwire({ paths: ['/.env'] })],\n});\n\napp.addHook('preHandler', async (request) => {\n  const decision = request.webdecoyDecision;\n  if (!decision) return;\n  console.log({\n    route: request.routeOptions.url,\n    conclusion: decision.conclusion,\n    tripwire: decision.deniedBy('tripwire'),\n    dashboardConfigured: Boolean(apiKey),\n  });\n});\n\napp.get('/health', async () => ({ ok: true }));\napp.get('/api/products', async () => ({\n  products: [{ id: 1, name: 'Field notebook' }],\n}));\napp.get('/.env', async (_request, reply) =>\n  reply.code(404).send({ error: 'Not found' })\n);\n\nawait app.listen({ host: '127.0.0.1', port: 4310 });\n```\n\nRegister 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.\n\nThe `/.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.\n\nAutomatic HTML trap injection is disabled here with `honeytoken: false`. The sample is a JSON API, and we only need request observation for this walkthrough.\n\nStart the server:\n\n```\nnode server.mjs\n```\n\nFrom another terminal:\n\n```\ncurl -i http://127.0.0.1:4310/api/products\ncurl -i http://127.0.0.1:4310/.env\n```\n\nThe product request returns HTTP 200 and a small catalog. The decoy path returns HTTP 404, while the terminal records:\n\n```\nroute: '/.env'\nconclusion: 'DENY'\ntripwire: true\ndashboardConfigured: false\n```\n\n`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.\n\nThe 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.\n\nThe 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.\n\nPut the key in a local `.env` file:\n\n```\nWEBDECOY_API_KEY=your_api_key\n```\n\nAdd `.env` to `.gitignore`. Keep the key on the server, including when you deploy. It does not belong in the frontend that calls this API.\n\nStop and restart the server with the file loaded:\n\n```\nnode --env-file=.env server.mjs\n```\n\nThen send the reserved test request:\n\n```\ncurl -i -A 'WebDecoy-Test/1.0' http://127.0.0.1:4310/api/products\n```\n\nOpen **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.\n\n`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.\n\nKeep your existing authentication, validation, authorization, and handlers. Add the plugin before the routes you want covered rather than replacing your server with this demo.\n\nThe 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.\n\nThis 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.\n\nAfter 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.\n\nDetections 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.\n\nStart 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.\n\nThe 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.\n\nNo API key was used in those checks. Cloud delivery and real-crawler identification are not claimed as local test results.", "url": "https://wpnews.pro/news/what-s-calling-your-fastify-api", "canonical_source": "https://dev.to/webdecoy/whats-calling-your-fastify-api-3kfh", "published_at": "2026-09-30 18:49:00+00:00", "updated_at": "2026-09-30 19:17:06.410316+00:00", "lang": "en", "topics": ["ai-crawlers", "developer-tools", "ai-agents"], "entities": ["WebDecoy", "Fastify", "Node.js", "FCaptcha"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/what-s-calling-your-fastify-api", "markdown": "https://wpnews.pro/news/what-s-calling-your-fastify-api.md", "text": "https://wpnews.pro/news/what-s-calling-your-fastify-api.txt", "jsonld": "https://wpnews.pro/news/what-s-calling-your-fastify-api.jsonld"}}