Node.js SDK
The WebDecoy Node SDK (@webdecoy/node) brings bot detection into your own backend: an in-process detection engine, deterministic tripwire rules, a self-hosted proof-of-work captcha, and JA3/JA4 TLS fingerprinting. Framework packages wrap it for Express, Fastify, and Next.js, and the Browser Client is its client-side counterpart.
Create a free account → · Compare all install methods
For the client-and-server architecture and package overview, see the Bot Detection SDK product page.
Installation
Section titled “Installation”npm install @webdecoy/nodeQuick Start
Section titled “Quick Start”The core method is protect(). Pass request metadata, get a decision:
import { WebDecoy } from '@webdecoy/node';
const webdecoy = new WebDecoy({ apiKey: process.env.WEBDECOY_API_KEY});
app.post('/api/submit', async (req, res) => { const { allowed, detection } = await webdecoy.protect({ method: req.method, path: req.path, ip: req.ip, // required user_agent: req.get('user-agent'), headers: req.headers });
if (!allowed) { return res.status(403).json({ error: 'Bot detected' }); }
// Process legitimate request});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 from 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, the request never reached the SDK: check that protect() runs for that route and that WEBDECOY_API_KEY is set (local-only mode has nowhere to report to).
Configuration
Section titled “Configuration”const webdecoy = new WebDecoy({ apiKey: 'sk_live_...', // optional, omit for local-only mode apiUrl: 'https://ingest.webdecoy.com', // default enableTLSFingerprinting: true, // default threatScoreThreshold: 70, // block above this score (default: 80) timeout: 5000, // request timeout (ms) debug: false});Per-request options on protect(metadata, options):
| Option | Description |
|---|---|
threshold |
Custom threat score threshold for this request |
skipLocalAnalysis |
Only use server-side detection |
metadata |
Extra data to include in the detection |
The Detection Response
Section titled “The Detection Response”protect() resolves to { allowed, detection, error?, ruleResult? }, where detection is:
| Field | Meaning |
|---|---|
decision |
allow, challenge, or block |
confidence |
Confidence score, 0–100 |
threat_level |
MINIMAL, LOW, MEDIUM, HIGH, or CRITICAL |
bot_detected |
Whether a bot was detected |
bot_type |
Type of bot, when detected |
detection_id |
ID for cross-referencing the Detections screen |
rule_enforced |
Whether a response rule fired |
The SDK fails open: if the detection call errors, the request is allowed and the error is surfaced on error.
Rules: Tripwires and Honeytokens
Section titled “Rules: Tripwires and Honeytokens”Rules evaluate locally, before any network call. A tripwire turns a hidden decoy link into a deterministic block:
import { WebDecoy, tripwire, honeytoken } from '@webdecoy/node';
// Generate a hidden decoy link + its secret pathconst trap = honeytoken(); // { path, linkHtml }
const webdecoy = new WebDecoy({ rules: [tripwire({ paths: [trap.path] })]});
// Inject trap.linkHtml into your pages. It's off-screen,// aria-hidden, and nofollow. Only scrapers ever request it.Rules Engine (no API key required)
Section titled “Rules Engine (no API key required)”The rules engine runs entirely inside the SDK. Rate limiting, filtering, and
tripwires work locally even with no apiKey configured:
import { WebDecoy, rateLimit, filter, tripwire, bots } from '@webdecoy/node';
const wd = new WebDecoy({ rules: [ // Fixed or sliding-window rate limiting, keyed however you like. // `window` is in seconds. rateLimit({ max: 60, window: 60, algorithm: 'sliding', action: 'THROTTLE' }),
// Filter expression language over request + enrichment data filter({ expression: 'ip.abuse_score > 80 or ip.tor', action: 'DENY' }),
// Honeypot paths that only scrapers ever request tripwire({ includeDefaults: true, action: 'DENY' }),
// Declared AI crawlers, by category bots({ categories: ['training_crawler'], action: 'DENY' }), ],});- Actions are
'DENY'or'THROTTLE'. - Every rule supports
dryRun: trueto evaluate and report without blocking. - Rule violations are batched to
POST /api/v1/sdk/violations/batch(when an API key is set) so they appear in your dashboard. - Filter expressions referencing IP intelligence use
GET /api/v1/sdk/ip/{address}/enrichment(cached in-SDK for an hour).
Blocking AI crawlers
Section titled “Blocking AI crawlers”bots() matches the agent registry that also powers
AI scraper scoring, so the category names
here are the same ones you see in your dashboard.
// Keep AI models out of your content, keep your search ranking.bots({ categories: ['training_crawler'] })
// Everything AI, minus the one you want referral traffic from.bots({ ai: true, allow: ['perplexitybot'] })
// A specific operator, slowed rather than blocked.bots({ agents: ['gptbot', 'ClaudeBot'], action: 'THROTTLE' })ai: true covers training_crawler, ai_search_crawler, ai_agent and
ai_assistant. It deliberately does not cover search_crawler, because blocking
Googlebot would deindex your site.
Filter expression fields
Section titled “Filter expression fields”| Namespace | Fields |
|---|---|
ip |
vpn, proxy, tor, relay, hosting, country, country_name, city, timezone, asn, asn_org, abuse_score, total_reports, is_high_risk |
req |
path, method, ip, user_agent, header("name") |
bot |
known, ai, category, name, id, organization, score, respects_robots |
edge |
present, class, clearance, verified, crawler, script, browser |
filter({ expression: 'bot.category == "training_crawler"' })filter({ expression: 'bot.ai and not bot.respects_robots' })filter({ expression: 'bot.category in ["generic_scraper", "headless_browser"]' })Comparisons against a missing value are always false, so a rule cannot accidentally fire on data that was never populated.
Verifying AI agents
Section titled “Verifying AI agents”detectBot() verifies Web Bot Auth signatures locally: no network call on the warm path, and no API key required. Pass a WHATWG Request or a plain { method, url, headers }:
const verdict = await webdecoy.detectBot(request);// verdict.status: 'verified' | 'impersonation' | 'claimed' | 'none'| Status | Meaning |
|---|---|
verified |
Signature checked out against the agent’s published keys |
impersonation |
It claimed to be a signing agent and the proof failed |
claimed |
Says who it is; nothing corroborates it |
none |
No agent claim at all |
To act on it, add the rule. It denies impersonation by default:
import { webBotAuth } from '@webdecoy/node';
const result = await webdecoy.protect(request, { rules: [webBotAuth()],});// result.agent carries the verdict either wayTwo design points worth knowing:
- Key directories are curated, never fetched from the request. A signature naming an arbitrary
Signature-AgentURL will not send the SDK to that host, so a malicious agent cannot use verification as an SSRF primitive. - Directories are cached with stale-while-revalidate, so a warm verification adds well under 5ms and never blocks on the network.
Requires @webdecoy/node 0.5.0 or later, and runs in edge runtimes as well as Node.
Self-Hosted Captcha
Section titled “Self-Hosted Captcha”The SDK ships a proof-of-work captcha you run on your own domain, with no third-party script:
import { Captcha } from '@webdecoy/node';
const captcha = new Captcha({ secret: process.env.WEBDECOY_SECRET });
// Verify a token submitted by the browser widgetapp.post('/login', (req, res) => { const { valid } = captcha.verifyToken(req.body.webdecoy_token, req.ip); if (!valid) { return res.status(403).json({ error: 'captcha failed' }); } // proceed});createCaptchaEndpoints() generates the challenge/verify HTTP handlers; the framework packages mount them for you, and the Browser Client provides the widget that solves them.
TypeScript
Section titled “TypeScript”Full type definitions ship with the package: WebDecoyConfig, RequestMetadata, ProtectResult, SDKDetectionResponse, and the rule and captcha types are all exported.
Next Steps
Section titled “Next Steps”- Express Middleware: drop-in protection for Express apps
- Fastify Plugin: the same for Fastify
- Next.js Middleware: edge middleware for Next.js
- Browser Client: the client-side half
- API Keys: get your
sk_live_key