Node 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.
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});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 } from '@webdecoy/node';
const wd = new WebDecoy({ rules: [ // Fixed or sliding-window rate limiting, keyed however you like rateLimit({ max: 60, window: '1m', algorithm: 'sliding', action: 'block' }),
// Filter expression language over request + enrichment data filter({ expression: 'ip.abuseScore > 80 or ua.isHeadless', action: 'block' }),
// Honeypot paths that only scrapers ever request tripwire({ includeDefaults: true, action: 'block' }), ],});- Every rule supports
dryRun: true— 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).
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 — 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