WebDecoy Is Now on WordPress.org: One-Click Bot Protection
WebDecoy is now live on the WordPress.org plugin directory. One-click install, automatic updates, and 100% local bot protection with no API key required.
bot-detectionScoring guesses, traps prove. Deception-based bot blocking on WordPress: tripwires, honeytokens, canary credentials, monitor mode, and WP-CLI.
Most WordPress bot protection works by estimating. It looks at a User-Agent, a request rate, a header set, and produces a guess. Good scoring gets you a long way — but every guess carries a false-positive tail, and on a checkout page that tail has a price.
There’s a second approach that doesn’t guess at all: set things that only a bot can touch.
This is a practical guide to doing that on WordPress. I’ll use the WebDecoy plugin for the concrete examples because it’s the one I work on and it’s free and local, but the technique is the point — you could build most of it yourself in an afternoon.
A detector says “this request is 80% likely to be automated.” A trap says “this request fetched a URL that appears nowhere in your sitemap, nowhere in your HTML, and is disallowed in robots.txt.”
The second statement isn’t a probability. A human browsing your site cannot accidentally do it.
That property is worth a lot, because the expensive failure in bot blocking isn’t the bot you miss — it’s the customer you block. A signal with no false-positive tail can drive a hard action (block, ban, refuse checkout) that you’d never dare trigger from a score alone.
So a sane architecture uses both: scoring for breadth, deception for certainty.
Hidden URLs that only crawlers and scanners request. Nothing links to them, they’re disallowed in robots.txt, and they don’t appear in your sitemap. Anything that requests one has either ignored robots.txt or is enumerating paths.
WordPress has an unusually rich set of these available, because attackers probe the same handful of things on every WP site:
/wp-content/plugins/<known-CVE-plugin>/…)/?author=1 style probing)These are the “public scanner” tier. They fire constantly on any site with a public IP, so treat them as a steady background signal rather than something to alert on.
An invisible decoy link injected into your pages. A human never sees it; a link-following scraper follows it.
The distinction from a tripwire path matters: a tripwire catches something guessing at URLs, a honeytoken catches something parsing your HTML and following every href. Different populations, and the second one is usually the scraper you actually care about.
Because nothing legitimate ever touches it, this is the one trap worth an alert.
A small war story on that: an earlier version of the plugin emailed the admin when the canary tripped. Sensible idea, terrible in practice — the canary link is on every public page, so busy sites got an email every hour, forever. It got removed one release later. Detections belong on a detections page, not in your inbox. If you build this yourself, learn from that: alert on the first trip per source, not every trip.
This is the sharpest one. Serve plausible-looking responses for the files attackers always probe:
/.env
/wp-config.php.bak
/backup.sql
/phpinfo.phpInstead of a 404, return a realistic file — seeded with per-site canary credentials that are valid nowhere.
Now you have a second-stage signal. Requesting /.env tells you someone is probing. Using the database password from that fake .env tells you someone read it and is trying it. That’s not reconnaissance any more, that’s an attempted intrusion, and it earns an immediate critical classification.
The per-site part matters: the canary has to be unique per install, or a single leaked credential list makes every site’s canary identical and useless.
If you run a store: a hidden coupon code that appears nowhere a customer could find it. Coupon-scraping bots harvest and try codes in bulk. Applying that code at checkout is proof of automation — there is no innocent path to it.
This one is my favourite because it sits exactly where the money is. Card-testing and coupon-abuse bots both hit checkout, and checkout is where a false positive costs you an actual order. A deterministic signal there is worth more than anywhere else on the site.
The plugin ships in monitor mode by default — it records what it would have done without doing it. That default is correct and you should respect it rather than flipping to blocking on day one.
I wrote a whole piece on why bot detection false positives are a business event, and the short version applies here: you cannot know your false-positive rate until you’ve watched real traffic, including the weird tail of it — corporate proxies, carrier-grade NAT, accessibility tooling, your own uptime monitors.
Run it in monitor mode across at least one full weekly cycle. Then look specifically at what would have been blocked and ask whether you recognise anyone in there.
# check current mode and counts
wp webdecoy status
# watch first
wp webdecoy config set mode monitor
# ...then, once the would-block list looks clean
wp webdecoy config set mode blockIf you manage client sites, you can lock the mode in wp-config.php so a settings save can’t silently drift it:
define( 'WEBDECOY_DEFAULT_MODE', 'monitor' ); // or 'block'
define( 'WEBDECOY_MAX_LOG_RETENTION', 90 ); // days, default 30One design detail I think is worth stealing: an unrecognised value for that constant is ignored rather than guessed. Forcing 'block' on a typo would start enforcing on a site that asked to watch; defaulting to 'monitor' would disarm one that asked to enforce. Neither is a safe guess, so it refuses to make one.
The failure mode nobody talks about with security plugins: it’s installed, the dashboard is green, and it isn’t actually inspecting anything — because a page cache sits in front of it, or the real client IP never arrives, or the scanner script is being stripped by an optimizer.
Two ways to prove it end to end:
Trip your own canary. The honeytoken’s secret path is shown in the settings with a “trip it now” link. Open it and watch the detection land. That exercises the full path: request → trap → scoring → storage → UI.
Hit it with the reserved test User-Agent:
curl -A "WebDecoy-Test/1.0" https://your-site.example/The plugin records a detection and answers with a 403 JSON receipt, so the curl output itself shows it acted. The test never blocks your IP, never trips enforcement rules, and never fires alerts.
That second one is the check I’d want in a deploy script. A green admin page proves the plugin is installed; a 403 receipt proves it’s in the request path.
The single most common way to hurt yourself here is blocking a crawler you needed.
Never allowlist by User-Agent string. Matching Googlebot and letting it through is a bypass, not an allowlist — anyone can send that string, and attackers do precisely because so many plugins trust it. Use forward-confirmed reverse DNS:
.googlebot.com)Any decent plugin does this for you — WebDecoy ships a list of 60+ verified crawlers — but check that yours does, because “recognises Googlebot” and “verifies Googlebot” are very different claims.
AI crawlers are a separate decision from search crawlers. GPTBot, ClaudeBot, PerplexityBot and friends can be blocked independently of Googlebot and Bingbot, and you want that as its own switch rather than robots.txt surgery. Worth deciding deliberately: some AI search engines send referral traffic worth having, while pure training scrapers return nothing.
Being honest about the limits, because “deception” can sound like a silver bullet:
wp plugin install webdecoy --activateOr Plugins → Add New → search “WebDecoy”. Requires WordPress 6.1+ and PHP 7.4+ (tested to 7.0.x). GPL, free, and with no API key it makes zero external connections — front end or back end. Detection data lives in your own database and cleans itself up after 30 days.
The mental model I’d leave you with: scoring tells you who’s probably a bot, deception tells you who definitely is. Most setups have plenty of the first and none of the second, and the second is the cheaper half to build.
Related reading:
Want this without building it? The plugin is free on WordPress.org and runs entirely on your own server. See the plans if you want confirmed attackers pushed out to Cloudflare or AWS WAF.
Yes. CAPTCHAs ask the human to prove themselves, which costs conversions and accessibility while commodity solver services defeat the challenge for a fraction of a cent. The alternative is invisible: honeypot fields on comment, login and registration forms, behavioural scoring of the submission, and a background SHA-256 proof-of-work challenge the browser solves without the visitor noticing. Add deception traps on top and you get a deterministic signal that never asks a person to do anything.
Detection estimates. It looks at the user agent, header set, request rate and behaviour, then produces a probability that the visitor is automated. Deception does not estimate: it plants things that only automation can touch, such as a hidden path nothing links to, an invisible decoy link, a fake .env seeded with canary credentials, or a decoy WooCommerce coupon. A human browsing normally cannot trip one by accident, so a trip is evidence rather than a guess. Use scoring for breadth and deception for certainty.
Only if you verify crawlers by user-agent string, which is a bypass rather than an allowlist because anyone can send the text Googlebot. Verify with forward-confirmed reverse DNS instead: resolve the requesting IP to a hostname, check the hostname ends in a verified domain such as .googlebot.com, then forward-resolve that hostname and confirm it returns the original IP. Blocking AI training crawlers should also be a separate switch from search crawlers, so you can refuse GPTBot without touching Googlebot.
No. Start in monitor mode and leave it there for at least one full weekly cycle. Monitor mode records what the plugin would have done without doing it, which is the only way to discover your real false-positive tail: corporate proxies, carrier-grade NAT, accessibility tooling and your own uptime monitors all look unusual. Review the would-block list, confirm you do not recognise anyone in it, and only then switch to blocking.
A green admin page proves the plugin is installed, not that it sits in the request path. A page cache in front of it, a missing real client IP, or an optimiser stripping the scanner script will all leave the dashboard looking healthy while nothing is inspected. Prove it end to end instead: trip your own canary URL and watch the detection land, and send a request with the reserved test user agent so the response itself shows the plugin acted.
It replaces one for most WordPress sites, because it needs no DNS change, no proxy and no subscription, and it sees things a network WAF cannot: which form was submitted, which coupon was applied, which hidden trap was touched. What it does not do is absorb volume, because the request already reached PHP by the time it is judged. If you are being flooded rather than probed, use the application layer to identify who to block and push those decisions out to an edge WAF.
WebDecoy is now live on the WordPress.org plugin directory. One-click install, automatic updates, and 100% local bot protection with no API key required.
bot-detectionA hands-on Next.js guide to blocking AI crawlers: edge middleware to gate requests, honeypot routes to trap bots, and origin fingerprinting.
tutorialBuild a proof-of-work challenge system using Hashcash to stop bots without CAPTCHAs. Full tutorial with Node.js and browser code.
tutorialLike this post? Share it with your friends!
Get a personalized demo from our team.