Skip to content

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.

Terminal window
npm install @webdecoy/node

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
});
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):

OptionDescription
thresholdCustom threat score threshold for this request
skipLocalAnalysisOnly use server-side detection
metadataExtra data to include in the detection

protect() resolves to { allowed, detection, error?, ruleResult? }, where detection is:

FieldMeaning
decisionallow, challenge, or block
confidenceConfidence score, 0–100
threat_levelMINIMAL, LOW, MEDIUM, HIGH, or CRITICAL
bot_detectedWhether a bot was detected
bot_typeType of bot, when detected
detection_idID for cross-referencing the Detections screen
rule_enforcedWhether 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 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 path
const 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.

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).

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'
StatusMeaning
verifiedSignature checked out against the agent’s published keys
impersonationIt claimed to be a signing agent and the proof failed
claimedSays who it is; nothing corroborates it
noneNo 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 way

Two design points worth knowing:

  • Key directories are curated, never fetched from the request. A signature naming an arbitrary Signature-Agent URL 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.

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 widget
app.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.

Full type definitions ship with the package — WebDecoyConfig, RequestMetadata, ProtectResult, SDKDetectionResponse, and the rule and captcha types are all exported.