Fastify Plugin
@webdecoy/fastify registers the Node SDK as a Fastify plugin. Detection runs on every request and the result is attached to req.webdecoy. It runs in monitor mode by default: verdicts are recorded and reported, and every request is served. Set mode: 'enforce' to answer blocking verdicts with a 403 (or your onBlocked handler).
For package capabilities and positioning, see the Fastify bot detection product page.
Installation
Section titled “Installation”npm install @webdecoy/fastify @webdecoy/nodeQuick Start
Section titled “Quick Start”import Fastify from 'fastify';import webdecoyPlugin from '@webdecoy/fastify';
const fastify = Fastify();
await fastify.register(webdecoyPlugin, { apiKey: process.env.WEBDECOY_API_KEY, threshold: 70, skipPaths: ['/health']});
fastify.post('/api/checkout', async (req, reply) => { const { decision, confidence, bot_detected } = req.webdecoy;
if (decision === 'block') { return reply.code(403).send({ error: 'Request blocked' }); } if (decision === 'challenge') { return reply.code(429).send({ error: 'Verification required' }); }
// Process checkout...});The SDK does not read WEBDECOY_API_KEY on its own. Pass it as apiKey, as above. Without an apiKey the plugin runs local checks only and nothing reports to your dashboard. Create the key under API Keys in the dashboard.
Verify Your Install
Section titled “Verify Your Install”Send one request with the reserved test User-Agent. It always fires a detection through the real pipeline, works on localhost before you deploy, and shows up labeled Test in the dashboard (excluded from stats and billing):
curl -A "WebDecoy-Test/1.0" http://localhost:3000/Within a few seconds a detection with the Test category chip appears on the Detections page. If nothing appears, check that the plugin is registered before your routes and that apiKey is passed with your key.
Options
Section titled “Options”Accepts the core SDK configuration and ProtectOptions (threshold, skipLocalAnalysis, metadata), plus:
| Option | Default | Description |
|---|---|---|
mode |
'monitor' |
'monitor' records verdicts and serves every request; 'enforce' blocks |
trustProxy |
Unset: uses request.ip |
How much of X-Forwarded-For to believe: a hop count, 'cloudflare', 'railway', or CIDRs of your proxies. See Client IP behind a proxy |
getIP |
Derived from trustProxy |
Custom IP extraction. Overrides trustProxy |
onBlocked |
403 Forbidden | Custom handler for blocked requests (enforce mode only) |
onError |
Log and allow (fail open) | Custom error handler |
skipPaths |
None | Paths to skip |
rules |
[tripwire()] |
Local rules; with none configured, the built-in tripwire paths are armed. Pass rules: [] to opt out |
honeytoken |
true when apiKey is set |
Inject a hidden trap link into HTML replies |
Client IP behind a proxy
Section titled “Client IP behind a proxy”Behind a load balancer or platform proxy, tell the plugin how many hops to trust, not Fastify:
await fastify.register(webdecoyPlugin, { apiKey: process.env.WEBDECOY_API_KEY, trustProxy: 1, // or 'cloudflare', 'railway', or ['10.0.0.0/8']});Since Fastify 5.12.1, a number passed to Fastify’s own trustProxy server option trusts no hop at all: a count cannot tell a proxy from a client connecting directly, so Fastify fails closed and request.ip stays the socket address. Every visitor then looks like your proxy. The plugin’s trustProxy counts from the right of X-Forwarded-For and is unaffected. Fastify’s CIDR and function forms of trustProxy still work, and the plugin uses request.ip when you leave its option unset.
Set a Scraper Trap
Section titled “Set a Scraper Trap”With an apiKey, this is already on. The plugin injects a hidden honeytoken
link into your HTML replies and arms the tripwire it points at:
import Fastify from 'fastify';import webdecoy from '@webdecoy/fastify';
const app = Fastify();await app.register(webdecoy, { apiKey: process.env.WEBDECOY_API_KEY });The path is derived from your API key by HMAC, so every replica advertises and
arms the same one. Set honeytoken: false to opt out.
Injection happens in an onSend hook and only rewrites full text/html replies,
so JSON, text and downloads are untouched and Fastify recomputes Content-Length
for you.
Captcha Endpoints
Section titled “Captcha Endpoints”webdecoyCaptchaPlugin mounts the self-hosted captcha endpoints for the Browser Client widget:
import { webdecoyCaptchaPlugin } from '@webdecoy/fastify';
await fastify.register(webdecoyCaptchaPlugin, { secret: process.env.WEBDECOY_SECRET});Next Steps
Section titled “Next Steps”- Node SDK: the core API, configuration, and detection response
- Browser Client: the widget for the captcha endpoints