Skip to content

Decoy Links & Honeypot URLs

Create one trap, make a controlled request, and inspect the resulting detection before choosing a response. You can start with the shared decoy domain without changing your site’s DNS.

Start free · Follow the first-decoy setup

Already have an account? Sign in, then follow the steps below. A controlled test proves that the trap reports; it does not demonstrate a real attack or guarantee blocking on your own domain.

A decoy link (also called a honeypot link) is a URL outside your normal visitor journey, placed to reveal unexpected access. A hit provides evidence to investigate: crawlers, security scanners, link previews and your own tests can all reach it. Inspect the context before treating it as an attack.

For use cases and product capabilities, see the Decoy Links product overview.

Your Website
├── /index.html ← Visible to users
├── /about.html ← Visible to users
├── /contact.html ← Visible to users
└── /admin-backup.zip ← DECOY (hidden, only bots find it)
│
▼
Bot accesses decoy
│
▼
Detection created
│
▼
Alert/Block action
  • Automated scanners crawl every link they find
  • Vulnerability tools probe common paths like /admin, /backup
  • Scrapers follow all links, including hidden ones
  • Reconnaissance maps your entire site structure
Traditional Security Deception Security
Tries to block known attacks Detects unknown attackers
Relies on signatures Catches by behavior
Must separate good traffic from bad Legitimate visitors never see the link, so a hit is a strong signal
Attackers can evade Attackers don’t know what’s a trap

  1. Navigate to Decoys

    • Click Decoys in the sidebar
  2. Click “Create Decoy”

    • The Create New Decoy dialog opens, with Honeypot Link selected
  3. Configure the Decoy

    Field Description Example
    Name Internal name for identification “Admin Backup Trap”
    Hosting Use the WebDecoy shared domain (default) or a custom domain you’ve verified Shared domain
    Path URL path for the decoy /admin/backup.zip
    Action on Detection What happens when accessed; begin with Log while testing Log
  4. Click “Create Decoy”

    • Your decoy is created and active immediately
  5. Verify one controlled request

    • Keep the action on Log and open the generated decoy URL once yourself.
    • Open Detections and match the record by the decoy and request time.
    • This is your own setup check. Do not count it as a real attack or assume it proves blocking works.
  6. Deploy the Decoy

    • Add a hidden link to your website pointing to the decoy URL
    • See Deploying Decoy Links for deployment methods

The URL depends on which hosting mode you picked:

Shared domain (default): WebDecoy generates a unique subdomain for each decoy.

https://{short_code}.d.webdecoy.com{path}

Example: https://abc12345.d.webdecoy.com/admin/backup.zip

A shared-domain decoy answers on any path under its subdomain, so every URL on abc12345.d.webdecoy.com reaches the same decoy.

Custom domain (optional): Uses the domain you’ve added under Settings → Domains.

https://{your-domain}{path}

Example: https://decoy.yoursite.com/admin/backup.zip

Both modes work the same for detection, scoring, and response actions, with one difference: clearance cookies are first-party, so they never reach the shared domain. The Clearance column on the Decoys list shows Can revoke for own-domain hosting (revocation is possible when that exact host receives a valid clearance cookie) and Detection only for shared-domain decoys (the bot is recorded but cannot be denied through its clearance).


The Decoys list separates evidence about a trap from the action configured for it:

Field What it tells you
Recorded accesses Retained request detections, excluding reserved tests and owner self-trips
Tests / Owner Separate retained test-trigger and authenticated owner-visit records
Link delivery: Unknown No acknowledged delivery tally is available for this decoy
Link delivery: At least N Authenticated serving-point acknowledgments in retained UTC event-day buckets, with coverage and freshness
Rate unavailable Delivery acknowledgments and access records do not provide compatible complete accounting for a percentage
Configured action The chosen response action, rather than proof that enforcement ran
Outcomes not attributed Confirmed enforcement outcomes are not currently attributed to this decoy in this summary

A recorded zero means no ordinary access records are currently retained. It does not tell you whether the link was installed, delivered, inspected, ignored or effective. A dash means counts were unavailable or the backend did not report them. Unknown delivery and enforcement counts stay unknown, including when recorded accesses are zero. Review Enforcement for the site’s actual enforcement evidence; capability and configured action are separate facts.

These are records, not unique visitors or complete lifetime request totals. Each stored detection ID counts once; a retried trap request may make a separate record. Storage admission, sampling, suppression and retention can omit or remove requests, so counts are a measured lower bound of recorded requests. Pending records/late arrival can change a later read. A cached page containing a trap does not create a delivery measurement, and a cached trap response that never reaches ingest cannot create an access record. Organization-wide decoys show their records across sites and retain the all sites label.

The legacy API field clicks_count and click-limit behavior retain their existing meaning: non-test records, including owner visits. Where owner visits exist, the list also shows Limit count to explain why that number differs from ordinary accesses. Tests remain outside the click limit, and test/owner visits remain exempt from billing, actor correlation and automatic enforcement. Pausing, expiry and first-party revocation behavior are unchanged.

List/detail decoy responses add an activity object. This is an example of a no-hit decoy with a successful record-count query:

{
"activity": {
"exposure_status": "unknown",
"trap_containing_responses": null,
"access_counts_status": "available",
"recorded_trap_accesses": 0,
"recorded_test_accesses": 0,
"recorded_owner_accesses": 0,
"count_basis": "retained_detection_records",
"enforcement_status": "not_attributed",
"confirmed_enforcement_outcomes": null,
"access_rate": null
}
}

A failed count query returns access_counts_status: "unavailable" with null record counts, rather than inventing a zero. Create/update responses report unavailable counts until a list/detail read supplies them. These read-only fields cannot be set by adding exposure numbers to decoy metadata.

A controlled server/edge response writer can report a trap-containing response to POST https://ingest.webdecoy.com/api/v1/decoy-deliveries. Create a private API key with Report Decoy Delivery (decoy-delivery:write) and select one registered property. Keep it on the server. The property must belong to that organization, and each generic decoy must be explicitly assigned to it. Organization-wide links and Shopify’s separate trap slugs need an explicit association/mapping; public site keys and browser beacons cannot authenticate this measurement.

The reference Node response writer records one event per decoy when a successful HTML response finishes, even when that link occurs several times. It creates a fresh response UUID at the serving point, including for a cached body. Cache hits require instrumentation at the cache-serving writer; origin_responses_only reports cannot measure them. A receipt means the writer offered a response, not that a visitor received every byte, inspected the link, followed it or was blocked.

For example, a no-hit decoy with three acknowledged responses can report:

{
"exposure_status": "measured_lower_bound",
"trap_containing_responses": 3,
"delivery_coverage": ["origin_responses_only", "cache_serving_point"],
"delivery_window_start": "2026-09-14T00:00:00Z",
"delivery_window_end": "2026-10-14T00:00:00Z",
"delivery_last_acknowledged_at": "2026-10-13T08:00:00Z",
"delivery_property_rejected_attempts_utc_day": 0,
"recorded_trap_accesses": 0,
"access_rate": null,
"confirmed_enforcement_outcomes": null
}

The dashboard shows At least 3, its reported origin/cache coverage, the UTC window start and latest acknowledgment. Reporter IDs are included in the API as delivery_reporter_ids. The window includes the current and previous 29 UTC event-day buckets; the end is exclusive. Late receipts can update earlier buckets. A tolerated future-dated next-day bucket appears on that UTC day.

Batches contain at most 100 events/32 KiB. Initial limits admit 60 new receipt pairs/property/minute and 10,000/property/UTC day across all reporter keys and replicas. Retrying the same organization/property/decoy/response identity adds nothing. Quota rejection is tallied as site receipt attempts rejected today; it is not a count of unique lost responses or a per-decoy count. Rejected retries can increment that tally again. Suppressed attempts cannot create unbounded receipt or reporter identities. Malformed, foreign/unassociated and stale batches are rejected atomically; no unverified body claim selects a tenant.

Exact dedupe IDs last seven days; aggregate buckets retain 30 UTC days. Cleanup uses indexed bounded deletes and may lag during an outage; reads exclude expired buckets. The producer’s bounded queue separately exposes outages, backoff, stale/overflow/shutdown discards. Unreported, sampled, lost or uninstrumented responses stay outside this acknowledged lower bound. No universal total is extrapolated.

An empty table remains Unknown, not a measured zero: there is no active heartbeat observation window. Access rates stay null because retained access records do not establish matching window, identity, cache/sampling completeness or retention. Delivery receipts do not supply an enforcement ledger link or change pause/expiry/click-limit/revocation behavior. Owned-site trace/dashboard reconciliation and budget evaluation remain tracked in #1585.

When someone accesses a decoy, WebDecoy can respond in different ways.

Action Behavior Best For
Log Record the access, return 200 OK Monitoring, intelligence gathering
Block Record and return 403 Forbidden Active defense, deterrence
Poison Return fake/misleading data Wasting attacker time
Redirect Redirect to another URL Directing to legal notices

The default action. Records the detection and returns a normal-looking response.

Response:

  • HTTP 200 OK
  • {"status": "ok"}

Use when:

  • You want to monitor without tipping off attackers
  • Gathering intelligence on attack patterns
  • Running decoys in observation mode

Records the detection and returns an access denied response.

Response:

  • HTTP 403 Forbidden
  • {"error": "access forbidden"}

Use when:

  • You want to actively deter attackers
  • Combined with integration blocking (Cloudflare, WAF)
  • Sending a clear “you’re caught” message

Returns fake data designed to waste attacker time or mislead them.

Response:

  • HTTP 200 OK
  • The JSON you enter as Poison Content. Without it, a generic {"status": "success", "data": [], ...} body

Examples:

  • Fake database records with invalid data
  • Honeypot credentials you can watch for elsewhere

Use when:

  • You want to actively mislead attackers
  • Wasting attacker resources
  • Creating uncertainty about what’s real

Redirects the request to another URL.

Response:

  • HTTP 302 Redirect
  • Redirects to the Redirect URL you configured (without one, it returns 200 with {"status": "ok"})

Use when:

  • Directing to a legal warning page
  • Sending to a law enforcement reporting portal
  • Redirecting to a CAPTCHA challenge

The create dialog has optional Click Limit and Expiry Date fields. When either is reached, the decoy stops answering exactly as a paused one does: requests to it return 404 and create no detection. The Decoys list shows it as Limit reached or Expired.

  • Click Limit (1 to 1,000,000) counts the same clicks as the list’s click column: every detection recorded for the decoy except test-trigger requests. The count is refreshed about every 10 seconds, so a decoy under heavy traffic can record a few more clicks than its limit before it stops.
  • Expiry Date takes effect at the start of the chosen day, in your browser’s time zone. It must be in the future.

To keep a stopped decoy catching bots, Resume it from its menu. Resuming removes the limit that stopped it: the Expiry Date of an expired decoy, the Click Limit of one that reached it. You are asked to confirm first. A limit that has not been reached is kept, and pausing never changes either limit.

Add internal notes about the decoy:

Description: "Placed in robots.txt disallow section to catch bots
that ignore robots.txt rules. Monitoring campaign started 2025-01."

Use for:

  • Documenting the decoy’s purpose
  • Tracking which campaign it belongs to
  • Notes for team members

When using the “Poison” action, specify what fake data to return:

{
"users": [
{"email": "[email protected]", "password": "honeypot123"},
{"email": "[email protected]", "password": "canary456"}
]
}

Tips:

  • Make it look realistic
  • Include obvious honeypot markers for tracking
  • Don’t include real data

Decoys only work if attackers can find them. Here are methods to deploy decoy links.

Add an invisible link to your website:

<!-- Hidden decoy link - invisible to users, visible to bots -->
<a href="https://decoy.yoursite.com/admin/backup.zip"
style="display:none; visibility:hidden; position:absolute; left:-9999px;">
Admin Backup
</a>

Where to place:

  • Footer of every page
  • Inside a hidden <div>
  • In the <head> section as a <link> tag

Add decoys to robots.txt with “Disallow” - malicious bots often target these paths:

robots.txt
User-agent: *
Disallow: /admin/
Disallow: /backup/
Disallow: /admin/backup.zip # This is actually a decoy!

Why this works:

  • Legitimate bots respect robots.txt
  • Malicious bots specifically target disallowed paths
  • Creates a perfect trap

Embed decoy URLs in HTML comments:

<!--
TODO: Remove before production
Backup location: https://decoy.yoursite.com/db_backup_2024.sql
-->

Why this works:

  • Attackers often search source code for sensitive paths
  • Comments look like developer mistakes
  • Very convincing to automated scanners

Hide decoy URLs in JavaScript:

// Configuration - do not modify
var config = {
apiEndpoint: "/api/v1",
adminPath: "https://decoy.yoursite.com/admin/debug", // Decoy!
backupUrl: "https://decoy.yoursite.com/backup.tar.gz" // Decoy!
};

Create a fake sitemap with decoy URLs:

honeypot-sitemap.xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://decoy.yoursite.com/admin/config.php</loc>
</url>
<url>
<loc>https://decoy.yoursite.com/backup/database.sql</loc>
</url>
</urlset>

These paths are commonly targeted by attackers:

Category Example Paths
Admin /admin, /administrator, /wp-admin, /admin.php
Backup /backup.zip, /backup.sql, /db_backup.tar.gz
Config /config.php, /settings.json, /.env, /wp-config.php
Debug /debug, /phpinfo.php, /test.php, /info.php
API /api/admin, /api/debug, /graphql/admin
Files /passwords.txt, /users.csv, /credentials.json

Navigate to Decoys to see all your decoy links:

Column Description
Name Your internal name for the decoy (tagged all sites if it is shared by every site in the organization)
Status Active or Inactive
Clearance Can revoke or Detection only (see above)
Action Log, Block, Poison, or Redirect
Detections Number of detections recorded
Created Creation date
Actions Menu with Pause/Resume, Copy URL and Delete

Filter by Status:

  • All
  • Active
  • Inactive

Sort by:

  • Created (Newest)
  • Detections (Most)
  • Active Status

Use the Search short code box to find a decoy by its short code.

  1. Find the decoy in the list
  2. Click the menu (three dots)
  3. Select Copy URL
  4. The full URL is copied to your clipboard

To deactivate or reactivate a decoy:

  1. Find the decoy in the list
  2. Click the menu (three dots)
  3. Select Pause (or Resume)

A paused decoy no longer answers: requests to it return 404 and create no detection.

An expired or limit-reached decoy shows Resume instead of Pause. See Click Limit and Expiry Date for what resuming it changes.

  1. Click the menu (three dots)
  2. Select Delete
  3. Confirm the deletion
  4. The decoy is permanently removed

Note: Deleting a decoy keeps the detections it already recorded. They stay in your detection history, no longer linked to a decoy.


  • ✅ Use realistic-looking paths
  • ✅ Deploy multiple decoys across your site
  • ✅ Include decoys in robots.txt (as disallowed)
  • ✅ Rotate decoys periodically
  • ✅ Monitor detection patterns
  • ❌ Use obviously fake names like “honeypot.html”
  • ❌ Place decoys where legitimate users might click
  • ❌ Forget to deploy the link on your actual site
  • ❌ Use the same decoy across all properties
  • ❌ Ignore the detection data

Learn about more advanced honeypots: