Skip to content

Clearance Enforcement

Most bot defenses enforce on IP or fingerprint, both of which rotate. Clearance enforcement flips the model: real browsers earn a signed wd_clearance token, and enforcement checks one thing per request: does this request carry a valid token? A bot on 10,000 IPs still needs a valid token on each request and can’t mint one. A real human on any IP passes silently.

The rotation-proof part is the loop with your deception surfaces: if a session that holds a token trips one of your decoys (or an SDK tripwire planted in your own pages), WebDecoy denies that browser’s fingerprint: its token is revoked within about a minute and it can never mint another, on any IP.

At the edge, each request passes if any of these hold, checked in order:

  1. Machine service token: a credential you issued for a partner, monitor, or job (see Tokenless clients).
  2. Verified bot: bots doing something you allowed, such as search crawling or link previews (verified by the platform / reverse-DNS, never by User-Agent).
  3. Unscoped route: the path isn’t in your token-enforced route list, so no token is required.
  4. Valid wd_clearance cookie: a real browser that already passed the client check, and whose fingerprint hasn’t been denied by tripping a decoy.

If none hold:

  • Monitor mode: the request passes anyway, tagged with an x-wd-clearance header so you can see what enforce mode would do.
  • Enforce mode: the browser gets a one-time invisible check that mints a token and reloads; non-browser clients get a 403.

A path you mark Monitor only is decided the same way but never refused, even while the site enforces. See Watch a path before refusing on it. A path you except from a protected path needs no clearance at all, for any caller. See Except a path inside a protected path.

Configure everything on the Enforcement page (sidebar → Enforcement). It is a set of routed sub-pages, which separates the things you set once from the things you come back to:

Sub-page What it owns
Overview (/enforcement) the Monitor/Enforce switch and the readiness checks that must pass before Enforce is available, the decision queue, and request activity
Policy (/enforcement/policy) protected paths and verification requirements, organization-wide access exceptions, and optional suggestions for other paths
Actions (/enforcement/actions) Response Actions
Setup (/enforcement/setup) saved WAF connections, browser verification, and the edge validator; completed configuration forms open with Manage
History (/enforcement/history) the audit log, and restoring an earlier policy

Old links are forwarded: /response-actions redirects to the Actions sub-page, and bookmarked ?tab= URLs land on the sub-page that replaced them.

A failing readiness check on Overview offers to take you to the control that fixes it, crossing to Setup or Policy as needed, so you never have to work out which of five places owns a problem. The one check that leaves Enforcement entirely is the decoy check, because nothing on this page can fix an off-origin decoy.

History can be filtered by kind of change and narrowed to the last 24 hours, 7 days or 30 days, and rows whose change left a saved configuration offer to restore it. A restore covers policy only: mode, scoped routes with their grades, whether each one refuses or only watches, and their exceptions, and the crawler allowlist. Signing keys, machine credentials and the deny-list are never restored, because reinstating those would reverse a security decision somebody made on purpose. See Session Clearance for the full boundary.

The Setup page summarizes what is installed. WAF connection rows name the provider, destination, and site assignment; a connected integration does not mean a blocking rule has been deployed. Completed configuration forms stay closed until you select Manage. Unfinished steps open automatically, and links from readiness checks open the relevant section.

The Cloudflare rule-permission test lives under WAF connections. Browser verification contains the public site key and optional Turnstile settings. The edge validator summary includes its recorded host and latest verified report when available.

It also says which protected-path features this site’s validators actually apply, such as per-path verification requirements. Validators you deployed earlier, or on a different platform, can ignore a setting that the Policy page still saves, so the answer comes from what verified validator reports declare over the last 7 days:

Status Meaning
Applied Every validator that reported in the last 7 days applies it.
Not applied None of them do. The setting is saved but not enforced there.
Partly applied Some do and some do not, so requests may be handled either way.
Not confirmed recently No validator reported in the last 7 days; the page shows what the last reports said, and when.
Not confirmed No verified report yet.

This describes the validators that reported. A validator that served no traffic sends no report, so it cannot rule out an older deployment still running somewhere.

The @webdecoy/client script (published on npm; the tag below loads it from the jsDelivr CDN) mints the wd_clearance cookie for real browsers during normal browsing. Add it to your site with your site key (shown on Enforcement → Setup, step 2, Browser verification → Manage):

<script src="https://cdn.jsdelivr.net/npm/@webdecoy/client/dist/webdecoy.global.js"
data-site-key="YOUR_SITE_KEY"
async></script>
Attribute Required Description
data-site-key yes Your publishable site key (the organization ID).
data-ingest no WebDecoy ingest origin. Defaults to https://ingest.webdecoy.com.

A clearance token belongs to your organization, not to one site or one path. Browsers send it only to the domain that set it, and any site in your organization accepts it, subject to its expiry, the fingerprint deny list, and the verification required by the path it is used on. To ask for stronger proof on a sensitive path, set that path’s Verification required level (see Trust levels).

Minting is deferred to browser idle time, runs at most once per session, and skips entirely when a fresh cookie already exists. It adds no page-load cost and never blocks paint or interaction.

The validator runs at your CDN edge and fails open on any error: if it can’t reach WebDecoy or can’t verify a token, it forwards the request untouched, so it can never take your site down. Pick the one matching your CDN:

The Cloudflare and AWS validators live in the public WebDecoy/edge repo, and Enforcement → Setup (step 3) pre-fills the config for your organization:

  • Cloudflare: one-click Deploy to Cloudflare installs the Worker into your account; then add a route for your domain using the pre-filled wrangler.toml variables shown in the dashboard.
  • AWS / CloudFront: a Lambda@Edge function on your distribution’s viewer-request behavior: set your keys in src/config.ts, build and zip, upload to S3 in us-east-1, then launch the provided CloudFormation template and attach the published function version to your distribution. Because AWS has no platform-verified-bot signal, this validator verifies crawlers itself via forward-confirmed reverse DNS.
  • Netlify: nothing to deploy. The WebDecoy Netlify extension’s injected function already runs on the site as a sensor; adding the environment variable shown on Enforcement → Setup (Functions scope) and redeploying turns it into the validator for that site. See Netlify.

On Enforcement → Setup, step 3 (Edge validator), enter your site domain and click Verify deployment. The dashboard checks, from your own browser, that the validator is answering on your zone, and reports its current mode. Since the validator fails open, this check is how you confirm it’s actually live before you rely on it. That panel also gives you the pre-filled config (your site key and route already filled in) for both Cloudflare and AWS.

This step is not optional. The token requirement applies only to paths you scope, so an organization in enforce mode with no scoped routes gates nothing: every request passes.

Scoping a path is what stops scrapers on it. A real browser earns a token in the background and never notices. Anything that cannot run JavaScript (curl, scrapers, most AI crawlers) gets a 403 it has no way to complete. This needs no fingerprint, no IP list and no machine learning; it works because the thing being asked for can only be produced by running JavaScript.

Add patterns under Enforcement → Policy, in Protected paths:

Pattern Matches
/checkout/* /checkout and everything beneath it
/api/orders that exact path
/* every path on the site

Start narrow. The paths worth gating are the ones that cost you when abused (checkout, login, search, expensive APIs), not your whole site.

On Enforcement → Policy, Review other paths shows the paths your sensors saw in the last 7 days that no route covers, sorted into what to do about each:

  • Consider protecting: paths whose shape says close them to automation (/checkout/*, /account/*). One click adds the route.
  • Probes (make these decoys instead): requests for software you may not run (/wp-admin/, /.env, /vendor/…). Scoping these protects nothing, because nothing is behind them; a decoy on the same path turns every attempt into a detection.
  • Public content (usually leave open), deliberately not suggested: usually your busiest paths. They are listed so you can see they were considered and rejected, not omitted.

Each suggestion shows the token that produced it (matched “/checkout”), because this is a heuristic reading your URL structure and it can be wrong. If something in the probe list is real on your site, scope it instead.

These are two different jobs, and conflating them is how a site loses its search traffic.

Gated routes Public content
What it does Enforcement: non-JS clients get a 403 Detection, impersonation evidence and accounting
Good for login, checkout, APIs, search, exports, anything expensive to serve articles, product pages, docs, and anything else you want indexed
How a crawler is handled blocked unless it is on your verified-bot allowlist observed, classified and counted; never blocked
What you get the abuse stops you learn who took what, and which of them lied about it

On public content the edge validator still annotates every request it forwards, so your origin can act without blocking anyone:

  • x-wd-clearance: the gate’s verdict (valid, missing, verified-bot, signed-agent, …)
  • x-wd-class: what the client is: verified, crawler, script, or browser

Read them with readEdgeVerdict() from @webdecoy/node, getEdgeVerdict() in a Next.js route handler, or the edge.* fields in a WordPress filter rule. That lets you meter, log or cheapen a response for scripted traffic without serving anyone a 403.

Leave enforcement in monitor mode to see the pass/challenge split before anything is actually blocked, then flip to enforce once it looks right.

In monitor mode every request is forwarded with x-wd-clearance set to the verdict enforce would have reached. You can read that from your logs, or from your application:

import { readEdgeVerdict } from '@webdecoy/node';
const edge = readEdgeVerdict(headers);
// edge.clearance === 'missing' → enforce mode would have challenged this request

The status card at the top of the Enforcement Overview shows the current mode and configured path patterns. “Enforcement is on — no protected paths” means enforcement is enabled but no paths have been selected, so requests pass through.

Once a site enforces, a new protected path starts refusing requests within a minute of being added. To try a path first without switching the whole site back to Monitor, add it with When the site enforces set to Monitor only.

A Monitor path never refuses anything:

  • Requests that only Monitor paths cover are served. They are counted on that path’s row as would have been refused, just as in site-wide Monitor mode.
  • A path that already refuses keeps refusing. If /api/* refuses, watching /api/reports/* does not relax it. Before you save, the form names every refusing path that covers some or all of the new one.
  • Monitor paths do not count toward readiness, so a site whose only paths are Monitor paths is not shown as protected.

Each Monitor row answers one question: how many requests would starting to refuse on this path add?

Row shows Meaning
N additional requests would be refused, out of M evaluated Of the M requests to this path in the last 7 days, N were served and would have been refused. N can be 0.
No requests to this path in the last 7 days Nothing reached the path, so there is nothing to estimate yet.
Why they would be refused The refusal reasons behind N, kept apart from the path’s actual outcomes.

Each Monitor path is evaluated as if it were the only one to start refusing, with its own verification requirement or the one already enforced there, whichever is stricter. One request can therefore count toward several Monitor rows, and these figures are never added together or included in request totals. A site in Monitor mode has nothing to start refusing, so its Monitor rows show no estimate.

When the numbers look right, select Start refusing on the row. The confirmation repeats the estimate, and History records the change.

Monitor paths need a validator that applies them. Setup lists this as Monitor paths. An older validator ignores Monitor paths entirely: it refuses nothing on them and reports no estimate, and the row says so rather than showing a zero. The AWS Lambda@Edge validator does not apply Monitor paths yet.

Protecting a broad path such as /api/* or /* also covers endpoints that automation has to reach: a payment processor posting to /api/webhooks/payments, a health check, or files under /.well-known/. Those callers cannot earn a clearance token, and most webhook senders cannot add a custom header, so a service credential is not an option either.

On the protected path’s row, select Add an exception and enter the path to let through. An exception removes the clearance requirement from that path for every caller: the webhook sender, and equally any script or scraper. It does not let one sender through. Whatever protects the endpoint afterwards has to live in your application, for example verifying the provider’s webhook signature. The form says this before anything is saved.

  • Exact before prefix. Except /api/webhooks/payments rather than /api/webhooks/* when one endpoint is all that needs it. The form offers the exact path when you enter a prefix.
  • An exception belongs to its path. It removes only that path’s coverage. If another protected path also covers the excepted path, for example /api/webhooks/*, those requests are still checked there, and the form names that path.
  • Suggestions, never defaults. On /*, the row suggests /.well-known/* and /health. Nothing is excepted until you add it. Review other paths also flags protected paths that look like webhooks or health checks, with the word in the URL that made the guess.

Each protected path’s row counts the requests its exceptions let through, and Request activity shows them as Let through by exceptions, apart from requests checked and requests blocked. Credentials, verified bots and signed agents are still counted as themselves on an excepted path.

Exceptions need a validator that applies them; Setup lists this as Path exceptions. A validator built before exceptions keeps requiring clearance on the excepted path, and the row says so. The AWS Lambda@Edge validator does not apply exceptions yet.

Limit a service credential to what it is for

Section titled “Limit a service credential to what it is for”

A service credential lets one of your own machine clients, or a partner’s, reach protected paths without browser verification. Until you limit it, it grants access to every protected path on every site in your organization: a reporting partner given /api/reports/* was also given /admin/*.

Under Enforcement → Policy → Access exceptions → Service credentials, creating a credential asks where it may grant access. Tick the sites and the protected paths it is for, or tick nothing, which is All protected paths on all sites and is what credentials have always been. Each credential’s row says what it reaches.

Outside its reach, a credential grants nothing. That is not the same as refusing. A request carrying it is then checked like any other request, so a valid clearance token, an exception, or a verified-bot exemption can still let it through. Limiting a credential takes access away from the credential, not from everyone holding one.

Existing credentials are unchanged. They name no reach, they are served exactly as before, and nothing about a deploy narrows them.

A limit is applied by the validator, so it needs one that understands limits. Setup lists this as Service credential limits.

The safety rule is that a limited credential is never sent to a validator that cannot apply the limit. That validator then ignores the credential entirely, so it can never grant more than you allowed. The consequence is worth stating plainly: on such a site the credential grants nothing, including on the paths you chose, and requests using it are checked like any other request.

So when you save a limit that names a site whose validators do not apply limits, the form says so before saving, names the site, and offers two choices: leave it unlimited for now, or limit it anyway knowing where it will not grant access. Redeploy that site’s validator to apply the limit properly.

The AWS Lambda@Edge validator does not apply credential limits. It is never served a limited credential, so it cannot over-grant one.

Noticing a credential used where it should not be

Section titled “Noticing a credential used where it should not be”

Each limited credential’s row counts the requests that presented it where it grants nothing, over the last seven days. It is a signal, not proof: a partner whose configuration drifted and somebody replaying a stolen credential look the same from here.

Only genuine credentials are counted. The edge counts a presentation only after the signature verifies, the credential belongs to your organization, the token is a machine token, and the id is one you issued, and the backend checks the ids again. Nobody can make one of your credentials look misused by claiming its id.

Sites whose validators do not report this are named as not covered rather than shown as zero: a site that never reports and a credential nobody misused would otherwise look identical.

Choose which bots are exempt, by what they do

Section titled “Choose which bots are exempt, by what they do”

Some bots should reach a protected path without proving they are a browser: the search crawlers that index you, the preview fetcher that builds a card when someone shares your link. Others should not, and “AI” is not one answer. An assistant fetching a page because a person just asked about it, and a crawler collecting pages in bulk to train a model, are different requests with different value to you.

Enforcement → Policy → Access exceptions asks what a verified bot is for, and lets you tick the ones you want through:

What they do Who that is
Search Googlebot, Bingbot, and AI search engines
Assistants acting for a person ChatGPT or Claude opening a link somebody sent
AI training crawlers GPTBot, ClaudeBot, and other bulk collectors
Link previews Slack, iMessage, WhatsApp, social platforms
Feed readers Feedly and other subscription readers
Uptime and analytics Pingdom, UptimeRobot, analytics services
SEO tools Ahrefs, Semrush, and similar crawlers
Security scanners Scanners run by you or your vendors
Everything else verified Verified bots whose purpose is none of the above

Only bots the network has verified are exempt at all. A crawler that merely claims to be Googlebot in its User-Agent is not verified and is checked like anything else, so an exemption cannot be taken by pretending.

Three things are worth knowing:

  • The boxes start from what you already allow. Before anyone chooses, they show what your current settings let through, and the card says so. This is not a claim that somebody picked them.
  • Search and training used to travel together. The earlier control had one switch covering AI assistants, AI crawlers and AI search, so allowing AI search allowed training crawlers too. Choosing by behavior separates them.
  • Older validators get a narrower version. A validator that does not read behaviors is served the old switches, derived so it exempts no more than you chose, never more. Setup lists this as Verified-bot exemptions by behavior, and the card says when a behavior you chose may still be asked to verify. Redeploy the validator to apply the choice exactly.

Each behavior shows what it actually let through over the last seven days, split by how the bot proved itself: verified by the network, or a signed agent. A behavior that let nothing through shows no count rather than a zero, because a validator that never reported and a bot that never came look the same in a zero.

The Enforcement overview shows the actual Protected paths, current mode, and validator status. Expand Setup complete to inspect individual checks; checks that need attention include a link to the relevant setup control.

Request activity reports the selected site’s activity over the last seven days. The exact start and end times are shown in UTC. Counts reflect the paths and mode configured when requests arrived, not just today’s settings.

Metric Meaning
Requests checked Requests a protected path decided, in both Monitor and Enforce periods
Requests blocked Requests refused while Enforce mode was active
Would have blocked Requests that failed checks during Monitor mode but were allowed through
Allowed outside protected paths Requests let through by a service credential, a verified bot or a signed agent on a path no protected path covers; they were never checked against one
Let through by exceptions Requests to a path you excepted from a protected path, from any caller; they were not checked
Requests on other paths Requests outside protected paths; access rules were not evaluated

By protected path splits checked requests by where they were decided. Each request appears on exactly one line, in the mode that decided it, and the lines add up to Requests checked:

Line What it holds
A protected path Requests that path decided. A credential, verified bot or signed agent on that path counts here.
No longer protected A path you removed during the window. Its counts stay on their own line rather than moving to another path.
Path not reported Checked requests from validators that do not report per path, and history from before they did. Redeploying the validator from Setup starts per-path reporting.
Rules in your application Requests refused by SDK rules, which are not protected paths.

On the Policy page, each protected path shows the same counts for the last 7 days, why its requests were refused, and, when a broader path sets a stricter requirement, which path that is. Where this site’s validators do not report per path, the row says so instead of showing a count.

Requests on other paths can include intentionally public content. Use Review protected paths to change coverage. Request counts may be incomplete because validators can stop before sending a report.

Expand Reporting details for the report count and any excluded activity. Older or unidentified installations can produce outcomes that cannot be verified or attributed to the selected site. Those outcomes are excluded from the site counts; historical exclusions do not establish the current protection status. Check the validator status in Setup if reporting needs attention.

Organization activity separately shows clearance requests refused across all sites for the same period. This counts token issuance attempts, not unique actors or requests blocked on the selected site.

When no verified activity has been reported, the page directs you to validator status rather than implying that zero activity proves protection. A failed activity read is shown as unavailable.

A token is not simply valid or invalid. It carries a grade reflecting how much positive human evidence the session presented when it was minted:

Grade Earned by Who can reach it
clean any real browser that hasn’t tripped deception everyone
human-likely one positive human signal: a browser-integrity check, interaction cadence, or browser-like TLS everyone
attested-human a passed Turnstile or Apple Private Access Token challenge anyone you’ve configured attestation for

A route can require a minimum grade, so / accepts any valid token while /checkout/* demands human-likely or higher. A session below the bar is challenged there and passes everywhere else.

The minimum is a control on the route, not an API call. Every row under Enforcement → Policy, in Protected paths, shows a Verification required selector. Expand Add a protected path to create a rule. Each option states what it costs a real visitor rather than naming a grade and leaving you to guess:

Verification required Token grade What it asks of a visitor
Browser check (default) clean A valid browser clearance token
Human signals human-likely A clearance token with a positive human signal
Verified challenge attested-human A verified challenge response; configure Turnstile in Setup first

The labels do not change the underlying API values. Access exceptions shows which bot behaviors are allowed through and what each one let through, with service credentials in a separate expandable section. Review other paths contains traffic-based suggestions; these do not become rules until you add them.

Changing a grade writes in place and is recorded as its own kind of audit entry, separate from adding a route, because the blast radii differ: raising a bar can start refusing visitors who were passing a moment ago, on a path that was already gated.

Four properties are deliberate:

  • Absent means clean. Tokens minted before grading existed, and validators too old to understand it, keep working exactly as before. Routes you scoped earlier are unaffected until you set a grade on them.
  • Evidence only ever raises a grade. A session that presents none (a keyboard-only visitor, someone on an older browser) mints the same clean token it always did. Nothing here can block a request on its own.
  • A bot-like threat score caps the grade. attested-human needs a threat score of 40 or below and human-likely 60 or below, so positive evidence cannot lift a token whose actor is already known to be bot-like. A real device can still be driving an attack, and conflicting evidence wins.
  • Validators that predate graded trust ignore the minimum and enforce presence alone, so raising it is safe to roll out before every edge is redeployed.

For automation, the same two fields are available over the API. A new route carries its grade on creation; changing an existing one is a PATCH, because re-posting a pattern that already exists returns the stored row untouched and would report success while nothing moved:

POST /organizations/{organization_id}/enforcement/routes
{ "pattern": "/checkout/*", "min_trust": "human-likely" }
PATCH /organizations/{organization_id}/enforcement/routes/{route_id}
{ "min_trust": "attested-human" }

Enforcement keys on two values, and they do different jobs:

Identity What it is What it enforces
JA4 the TLS handshake fingerprint, set by the client’s software and not by whoever wrote the User-Agent fingerprint rules pushed to your WAF
Clearance fingerprint the identity inside a wd_clearance token, derived from stable browser characteristics deny-at-mint, which survives IP rotation

Both are returned on a detection and can be copied from its flyout, along with the IP address and ASN, so arming your WAF is not manual transcription off the screen. When a detection carries its own clearance fingerprint, that value is used in preference to the actor’s rolled-up one: the rollup holds whatever is minting tokens now, while the detection holds what arrived with the request you are looking at.

  • The wd_clearance cookie is a first-party cookie on your domain, which is why the decoy → deny loop needs a same-origin decoy. See the caution at the top, where this belongs rather than in a notes list.
  • The device fingerprint uses stable browser characteristics (canvas, WebGL, screen, timezone, platform, language). Privacy browsers that randomize canvas will simply re-mint each visit, and enforcement still holds within a session.
  • No User-Agent is used in the fingerprint, so a browser update doesn’t churn a user’s identity.