<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: webdecoy</title>
    <description>The latest articles on DEV Community by webdecoy (@webdecoy).</description>
    <link>https://dev.to/webdecoy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F96509%2F575bf9aa-10c6-43d0-9624-7f180f9fabfe.png</url>
      <title>DEV Community: webdecoy</title>
      <link>https://dev.to/webdecoy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/webdecoy"/>
    <language>en</language>
    <item>
      <title>Why crawler logs and browser analytics disagree: a five-check debugging guide</title>
      <dc:creator>webdecoy</dc:creator>
      <pubDate>Fri, 23 Oct 2026 01:09:51 +0000</pubDate>
      <link>https://dev.to/webdecoy/why-crawler-logs-and-browser-analytics-disagree-a-five-check-debugging-guide-1ol8</link>
      <guid>https://dev.to/webdecoy/why-crawler-logs-and-browser-analytics-disagree-a-five-check-debugging-guide-1ol8</guid>
      <description>&lt;p&gt;Your server logs show requests to an article. Your browser analytics dashboard shows almost nothing for that page. Which one should you trust?&lt;/p&gt;

&lt;p&gt;Start with what each system measures. A request log records requests at a particular point in your infrastructure. A browser analytics tag depends on code running and a measurement reaching its collection endpoint. Those are different events.&lt;/p&gt;

&lt;p&gt;I work on WebDecoy, where we build tools for investigating bot and AI traffic. Here is a debugging sequence you can use with your existing logs and analytics, before adding another tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Match the scope before comparing counts
&lt;/h2&gt;

&lt;p&gt;Choose one hostname, one page and a short time window. Write down the timezone for both reports.&lt;/p&gt;

&lt;p&gt;For example, compare requests for &lt;code&gt;/pricing/&lt;/code&gt; between 14:00 and 14:15 UTC with browser events for that same page and interval. This is a hypothetical example, not a customer measurement.&lt;/p&gt;

&lt;p&gt;Check whether one report combines subdomains, includes query strings, or normalizes trailing slashes differently. Separate HTML requests from assets and API requests. Check status codes too: a redirect, an error response and a successful HTML response do not produce identical browser behavior.&lt;/p&gt;

&lt;p&gt;Even after aligning the scope, a request count and a pageview count are different units. Do not expect them to match exactly.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Identify where the request was observed
&lt;/h2&gt;

&lt;p&gt;A CDN can serve a cached response without contacting your origin. An application-level logger may therefore miss traffic that an edge log records.&lt;/p&gt;

&lt;p&gt;Write down the observation point for each source:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;What it can establish&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Edge request log&lt;/td&gt;
&lt;td&gt;A request reached that edge and received the recorded response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Origin/application log&lt;/td&gt;
&lt;td&gt;A request reached that origin or application handler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser analytics event&lt;/td&gt;
&lt;td&gt;The instrumentation sent an event that the analytics system recorded&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Check sampling, retention and filtering before treating any of these as a complete inventory.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Inspect the HTML that a client actually receives
&lt;/h2&gt;

&lt;p&gt;For a page you own or are authorized to test, inspect an ordinary response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--silent&lt;/span&gt; &lt;span class="nt"&gt;--show-error&lt;/span&gt; &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dump-header&lt;/span&gt; /tmp/page-headers.txt &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; /tmp/page.html &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'https://your-site.example/pricing/'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the example address with your own page. This command downloads the response; it does not execute the page's JavaScript.&lt;/p&gt;

&lt;p&gt;Compare the saved HTML with the rendered DOM in your browser. Is the main text present in the response? Does it appear only after JavaScript runs? Is the analytics tag included on this route?&lt;/p&gt;

&lt;p&gt;Google's JavaScript SEO documentation distinguishes fetching HTML from rendering JavaScript and notes that not all bots can run JavaScript. Avoid assuming every crawler behaves like Googlebot, or like the browser you use for testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Test the browser measurement path separately
&lt;/h2&gt;

&lt;p&gt;In your browser's network panel, inspect whether the expected analytics request is sent. Then use your analytics provider's debugging view, where available, to check receipt.&lt;/p&gt;

&lt;p&gt;Consent choices, content blockers, network failures and missing route instrumentation can all affect measurement. A missing browser event does not, by itself, identify a bot.&lt;/p&gt;

&lt;p&gt;Label your own setup tests and exclude them from claims about real visitors. A successful test proves that the tested path reports under those conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Keep identity claims separate from request evidence
&lt;/h2&gt;

&lt;p&gt;A user-agent string is a client's claim. A data-center IP describes infrastructure. Neither establishes that a particular AI provider made the request, that the traffic is malicious, or that a human was absent.&lt;/p&gt;

&lt;p&gt;Use the identity-verification method documented by a provider when available. Preserve the distinction between a declared crawler and a verified one. For other traffic, inspect request patterns and relevant session evidence before deciding how to respond.&lt;/p&gt;

&lt;p&gt;Also keep crawls, citations and referrals separate. A page fetch does not prove that an assistant cited it or sent a person back to your site.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn the discrepancy into a useful finding
&lt;/h2&gt;

&lt;p&gt;A useful conclusion looks like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In this window, the edge recorded HTML requests that were absent from the browser event stream. We checked the hostname, path, response codes and browser instrumentation. Request identity remains unverified.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is evidence you can investigate. It gives you a better next step than classifying every unmatched request as an AI bot.&lt;/p&gt;

&lt;p&gt;If you want to inspect bot and AI traffic through WebDecoy, choose an integration that covers the traffic you need to observe. Browser instrumentation covers executing clients; server and edge integrations cover different request paths. &lt;a href="https://webdecoy.com/integrations/?utm_source=devto&amp;amp;utm_medium=community&amp;amp;utm_campaign=crawler-visibility&amp;amp;utm_content=five-check-guide" rel="noopener noreferrer"&gt;Explore WebDecoy and the available integrations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Follow WebDecoy here for practical detection diagnostics. If you have a measurement mismatch, share the observation points and the counts' definitions in the comments, with private data removed.&lt;/p&gt;

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics" rel="noopener noreferrer"&gt;Google: Understand JavaScript SEO basics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.cloudflare.com/web-analytics/faq/" rel="noopener noreferrer"&gt;Cloudflare: Web Analytics FAQs&lt;/a&gt;, including the distinction between JavaScript beacon analytics and edge analytics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Drafted with AI assistance and reviewed for the scope of each technical claim. No production accuracy benchmark or customer outcome is claimed here.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>javascript</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Testing WooCommerce checkout protection caught a plugin load-order bug</title>
      <dc:creator>webdecoy</dc:creator>
      <pubDate>Wed, 21 Oct 2026 19:37:46 +0000</pubDate>
      <link>https://dev.to/webdecoy/testing-woocommerce-checkout-protection-caught-a-plugin-load-order-bug-1e07</link>
      <guid>https://dev.to/webdecoy/testing-woocommerce-checkout-protection-caught-a-plugin-load-order-bug-1e07</guid>
      <description>&lt;p&gt;I work on WebDecoy. We set out to record a WooCommerce checkout-protection demo. Testing both checkout paths first found a real bug in our plugin.&lt;/p&gt;

&lt;p&gt;The test used three rapid, synthetic failed orders from one test IP, then a fourth checkout attempt. A separate normal test client used an offline gateway to check that a normal order could still complete.&lt;/p&gt;

&lt;p&gt;No real cards or payment processor were involved.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/pYzsU-EGO_0" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Blocks passed. Classic did not.
&lt;/h2&gt;

&lt;p&gt;The Store API used by Checkout Blocks refused attempt four with HTTP 403 and &lt;code&gt;webdecoy_carding&lt;/code&gt;. Classic checkout kept reaching the local decline gateway.&lt;/p&gt;

&lt;p&gt;The checkout-protection setting was enabled. The validation callback was missing.&lt;/p&gt;

&lt;p&gt;Our plugin initialized its hooks while WordPress was including the plugin entry point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'protect_checkout'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;class_exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WooCommerce'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'woocommerce_checkout_process'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'check_checkout'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="c1"&gt;// payment and checkout-attempt tracking callbacks&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When WebDecoy loaded before WooCommerce, the class did not exist yet. The condition stayed false for that request, so classic validation and attempt tracking were never registered.&lt;/p&gt;

&lt;p&gt;A valid setting did not mean the integration was running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Register the integration after plugins load
&lt;/h2&gt;

&lt;p&gt;Version 2.10.6 defers classic integration registration until &lt;code&gt;plugins_loaded&lt;/code&gt;, after our includes load:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'plugins_loaded'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'register_woocommerce_hooks'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That method keeps the protection-option and WooCommerce-presence checks, then registers the classic validation, payment and checkout-attempt callbacks.&lt;/p&gt;

&lt;p&gt;We also added a runtime check to the WooCommerce compatibility smoke script. It checks the exact callbacks with &lt;code&gt;has_action&lt;/code&gt;, so a missing callback fails the smoke check before checkout testing begins.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/WebDecoy/wordpress-plugin/pull/116" rel="noopener noreferrer"&gt;The fix and validation are in PR #116&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repeat against the released package
&lt;/h2&gt;

&lt;p&gt;After release, we installed the official WordPress.org &lt;strong&gt;2.10.6&lt;/strong&gt; package in the isolated store and ran each checkout path independently, clearing only disposable test history between paths.&lt;/p&gt;

&lt;p&gt;Environment: WooCommerce 11.2.1, WordPress 7.1 and PHP 8.3. Each failure sequence used distinct guest carts containing a $3 virtual product, an ordinary browser user-agent, and a local gateway that always fails without contacting a processor.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Classic&lt;/th&gt;
&lt;th&gt;Blocks Store API&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Attempts 1–3&lt;/td&gt;
&lt;td&gt;Local synthetic decline&lt;/td&gt;
&lt;td&gt;Local synthetic decline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attempt 4&lt;/td&gt;
&lt;td&gt;JSON &lt;code&gt;failure&lt;/code&gt;, suspicious-checkout notice&lt;/td&gt;
&lt;td&gt;HTTP 403, &lt;code&gt;webdecoy_carding&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Separate normal offline order&lt;/td&gt;
&lt;td&gt;Accepted&lt;/td&gt;
&lt;td&gt;Accepted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Classic and Blocks use different response formats. A test expecting HTTP 403 on both would misread the classic response.&lt;/p&gt;

&lt;p&gt;The WordPress dashboard recorded a &lt;strong&gt;Woocommerce card testing&lt;/strong&gt; detection. That is the source category; the test exercised a synthetic failed-order pattern, rather than actual card authorizations. Checkout refusal is also separate from a sitewide IP ban.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would test on another store
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The checkout path actually deployed, including Blocks if used.&lt;/li&gt;
&lt;li&gt;A normal successful test purchase and legitimate payment retries.&lt;/li&gt;
&lt;li&gt;The gateway’s documented test mode and any offline gateways.&lt;/li&gt;
&lt;li&gt;Guest checkout, shared-network customers and checkout extensions.&lt;/li&gt;
&lt;li&gt;Monitor mode versus enforcement, plus any proxy-configuration notice suppressing enforcement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does not establish resistance to rotating proxies, production conversion impact or a false-positive rate. The local checks use client-IP checkout history, so shared networks and IP rotation matter. Gateway fraud controls remain useful alongside application checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/webdecoy/" rel="noopener noreferrer"&gt;WebDecoy Bot Detection is on WordPress.org&lt;/a&gt;. Local protection works without a WebDecoy account. Update to 2.10.6 or newer before testing classic checkout.&lt;/p&gt;

&lt;p&gt;Start in monitor mode, inspect the evidence, test your checkout in staging, and then review enforcement. The &lt;a href="https://webdecoy.com/blog/woocommerce-bot-protection-checkout-carding-fake-orders/?utm_source=devto&amp;amp;utm_medium=organic_social&amp;amp;utm_campaign=woocommerce_checkout_proof" rel="noopener noreferrer"&gt;full test and rollout guide&lt;/a&gt; includes the setup and limitations.&lt;/p&gt;

&lt;p&gt;Have you had a WooCommerce integration look enabled while its callbacks were missing? I would be interested in how you catch that in integration tests.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Written with AI assistance. Results come from the controlled tests described above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>ecommerce</category>
      <category>security</category>
      <category>testing</category>
    </item>
    <item>
      <title>AI crawlers found your site. Are they sending visitors back?</title>
      <dc:creator>webdecoy</dc:creator>
      <pubDate>Wed, 21 Oct 2026 03:06:20 +0000</pubDate>
      <link>https://dev.to/webdecoy/ai-crawlers-found-your-site-are-they-sending-visitors-back-10gm</link>
      <guid>https://dev.to/webdecoy/ai-crawlers-found-your-site-are-they-sending-visitors-back-10gm</guid>
      <description>&lt;p&gt;You can fix your sitemap, improve your headings, and make your content easier for machines to read. The next question is practical: &lt;strong&gt;are AI systems visiting, and are they sending people back?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We built a 35-second demonstration using our own site's data. I'm on the WebDecoy team; GEOMCP is our open-source audit project.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/nFJYOJ2ABFU" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with a technical change you can verify
&lt;/h2&gt;

&lt;p&gt;Use &lt;a href="https://github.com/WebDecoy/geomcp" rel="noopener noreferrer"&gt;GEOMCP&lt;/a&gt; from your MCP-compatible editor to audit a public page and inspect the evidence behind each finding. A useful starting prompt is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Audit &lt;a href="https://your-site.example" rel="noopener noreferrer"&gt;https://your-site.example&lt;/a&gt;. Show the three most useful fixes and the evidence for each. Recheck the affected pages after the changes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In our recorded October 17 audit, we checked 193 sitemap URLs and cleared 11 original warnings. One small example was the features page: 16 links, including one empty or generic label, became 16 links with no empty or generic labels.&lt;/p&gt;

&lt;p&gt;That is a verified technical improvement. Keep the audit output and the deployment date so you have a concrete change to compare against later.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Observe crawlers where their requests arrive
&lt;/h2&gt;

&lt;p&gt;A browser analytics tag only sees visits that execute it. Many crawlers never run JavaScript, so use server or edge observation to see those requests.&lt;/p&gt;

&lt;p&gt;In WebDecoy, select your property and open &lt;strong&gt;AI Traffic&lt;/strong&gt;. Choose a time window, then inspect the crawler breakdown alongside the overview. Keep AI-only crawling separate from operators that serve both search and AI.&lt;/p&gt;

&lt;p&gt;Our seven-day snapshot showed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measurement&lt;/th&gt;
&lt;th&gt;Observed value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AI-only pages crawled&lt;/td&gt;
&lt;td&gt;4,547&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Additional mixed search + AI crawling&lt;/td&gt;
&lt;td&gt;1,220&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI referral visits&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distinct referral landing pages&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are recorded counts for webdecoy.com, not a benchmark for every site. “Pages crawled” counts crawling activity; it does not mean 4,547 unique URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Follow the return visits
&lt;/h2&gt;

&lt;p&gt;Open &lt;strong&gt;Referrals&lt;/strong&gt; to inspect both the referring platform and the landing page. In this snapshot, ChatGPT, Gemini and Kagi each sent two observed visits; Claude sent one.&lt;/p&gt;

&lt;p&gt;The destination matters as much as the total. A tutorial or demo page receiving those visits gives you a specific page to improve: make its explanation clear, keep its example runnable, and give the reader an obvious next action.&lt;/p&gt;

&lt;p&gt;Use the page tag or edge sensor to observe referral visits. A crawler request and a referred human visit are separate events. A page can also appear in an AI answer without anyone clicking it, and missing referrer information can leave a visit unattributed.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Build a repeatable comparison
&lt;/h2&gt;

&lt;p&gt;Record the deployment date and affected URLs. Compare equal time windows and check the sensor coverage. Look at which pages are crawled, which platforms return visitors, and which pages those visitors land on. Measure your own signup or activation events separately if you want to understand what happens after arrival.&lt;/p&gt;

&lt;p&gt;Our audit and these traffic observations do &lt;strong&gt;not&lt;/strong&gt; establish that a technical fix caused a referral. They give us a way to inspect both sides of the loop and decide where to work next.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webdecoy.com/geomcp/?utm_source=devto&amp;amp;utm_medium=organic_article&amp;amp;utm_campaign=geo_crawlers_to_referrals" rel="noopener noreferrer"&gt;Try the GEOMCP setup and recorded audit&lt;/a&gt;, then connect your site to WebDecoy and inspect &lt;strong&gt;AI Traffic → Referrals&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Which part is hardest to measure on your site today: the crawl, the referring platform, or what visitors do after they arrive?&lt;/p&gt;

</description>
      <category>seo</category>
      <category>ai</category>
      <category>webdev</category>
      <category>analytics</category>
    </item>
    <item>
      <title>Bot su WordPress: cosa vede il plugin quando c’è una cache davanti?</title>
      <dc:creator>webdecoy</dc:creator>
      <pubDate>Tue, 20 Oct 2026 23:19:35 +0000</pubDate>
      <link>https://dev.to/webdecoy/bot-su-wordpress-cosa-vede-il-plugin-quando-ce-una-cache-davanti-2d0g</link>
      <guid>https://dev.to/webdecoy/bot-su-wordpress-cosa-vede-il-plugin-quando-ce-una-cache-davanti-2d0g</guid>
      <description>&lt;p&gt;Hai installato un plugin per rilevare i bot, ma il registro non mostra tutte le visite? Prima di cambiare le soglie, controlla dove viene servita la pagina.&lt;/p&gt;

&lt;p&gt;Una CDN o una cache può rispondere senza eseguire WordPress. In quel caso, il codice PHP del plugin non vede quella richiesta alla pagina. Questo dettaglio cambia il modo in cui interpreti i risultati.&lt;/p&gt;

&lt;p&gt;Faccio parte del team WebDecoy. Qui uso il nostro &lt;a href="https://wordpress.org/plugins/webdecoy/" rel="noopener noreferrer"&gt;plugin libero per WordPress&lt;/a&gt; come esempio concreto. Il testo italiano è stato preparato con assistenza IA.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guarda il registro cambiare in 20 secondi
&lt;/h2&gt;

&lt;p&gt;Nel video, una richiesta controllata al canary produce due rilevazioni nel registro locale di WordPress. La modalità monitor registra senza bloccare. È una prova del percorso di rilevazione, non un benchmark della cache: le verifiche con cache attiva restano da eseguire sul proprio sito. Il video contiene testo in inglese.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/y6eErUY1Vsc" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepara una prova ripetibile
&lt;/h2&gt;

&lt;p&gt;Usa un sito di staging, con una pagina pubblica e un modulo nativo di WordPress. Annota URL, ora del test e configurazione della cache. Non usare dati reali dei clienti.&lt;/p&gt;

&lt;p&gt;Installa WebDecoy dal catalogo di WordPress oppure con WP-CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp plugin &lt;span class="nb"&gt;install &lt;/span&gt;webdecoy &lt;span class="nt"&gt;--activate&lt;/span&gt;
wp webdecoy config &lt;span class="nb"&gt;set &lt;/span&gt;mode monitor
wp webdecoy status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La modalità monitor registra le rilevazioni senza bloccare le richieste. Il funzionamento locale non richiede account o chiavi API; il collegamento a WebDecoy Cloud è facoltativo. Il plugin richiede WordPress 6.1 o successivo e PHP 7.4 o successivo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separa tre percorsi
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Percorso&lt;/th&gt;
&lt;th&gt;Cosa verificare&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pagina non servita dalla cache&lt;/td&gt;
&lt;td&gt;La richiesta raggiunge WordPress? Quali eventi compaiono nel registro?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stessa pagina servita dalla cache&lt;/td&gt;
&lt;td&gt;Il server di origine viene contattato? Il codice del browser è ancora presente?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invio del modulo&lt;/td&gt;
&lt;td&gt;Quale endpoint riceve l’invio? Il controllo avviene anche se la pagina iniziale era in cache?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Non interpretare la mancanza di un evento come una prova di assenza di bot. Potrebbe mancare il segnale, oppure la richiesta potrebbe non aver raggiunto il componente che lo registra.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlla il modulo con un test controllato
&lt;/h2&gt;

&lt;p&gt;Prima completa un percorso normale: commento, login o registrazione, se disponibile. Poi, solo sul sito di prova, individua un campo trappola effettivamente generato da WebDecoy ed esegui un invio controllato che lo compili.&lt;/p&gt;

&lt;p&gt;Confronta ora, percorso ed evento nel registro. In modalità monitor stai verificando la rilevazione del segnale: non aspettarti un blocco.&lt;/p&gt;

&lt;p&gt;Per un modulo di terze parti, controlla l’integrazione specifica. Il fatto che un plugin protegga i moduli nativi di WordPress non significa che intercetti automaticamente ogni endpoint REST o modulo personalizzato.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non confondere una visita sospetta con un’identità
&lt;/h2&gt;

&lt;p&gt;WebDecoy combina esche, analisi delle richieste e segnali del browser. Questi elementi aiutano a investigare l’automazione, ma non identificano da soli una persona o uno specifico agente IA.&lt;/p&gt;

&lt;p&gt;Se il sito è dietro un proxy, controlla inoltre come viene determinato l’indirizzo del client. Una politica basata su un IP interpretato male può raggruppare visitatori diversi.&lt;/p&gt;

&lt;p&gt;Nel registro locale puoi esportare gli eventi in CSV e configurare la conservazione, impostata a trenta giorni per default. Confronta i risultati con i tuoi log prima di scegliere una risposta più restrittiva.&lt;/p&gt;

&lt;h2&gt;
  
  
  Il risultato utile del test
&lt;/h2&gt;

&lt;p&gt;Alla fine dovresti poter rispondere a tre domande:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Quali richieste arrivano davvero a WordPress?&lt;/li&gt;
&lt;li&gt;Quali segnali restano disponibili con la cache attiva?&lt;/li&gt;
&lt;li&gt;I percorsi legittimi continuano a funzionare?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Se non riesci a rispondere, raccogli prima queste prove. Cambiare una soglia non risolve un punto cieco nella raccolta dei segnali.&lt;/p&gt;

&lt;p&gt;Puoi &lt;a href="https://wordpress.org/plugins/webdecoy/" rel="noopener noreferrer"&gt;installare WebDecoy da WordPress.org&lt;/a&gt; e &lt;a href="https://github.com/WebDecoy/wordpress-plugin" rel="noopener noreferrer"&gt;leggere il codice su GitHub&lt;/a&gt;. Se provi questa verifica con una cache particolare, indica nei commenti configurazione e passaggi riproducibili, senza pubblicare IP dei visitatori o log privati.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>security</category>
      <category>tutorial</category>
      <category>italian</category>
    </item>
    <item>
      <title>Catch WordPress bots without an account: a five-check WebDecoy setup</title>
      <dc:creator>webdecoy</dc:creator>
      <pubDate>Tue, 20 Oct 2026 03:12:10 +0000</pubDate>
      <link>https://dev.to/webdecoy/catch-wordpress-bots-without-an-account-a-five-check-webdecoy-setup-4gi8</link>
      <guid>https://dev.to/webdecoy/catch-wordpress-bots-without-an-account-a-five-check-webdecoy-setup-4gi8</guid>
      <description>&lt;p&gt;WebDecoy detects automated traffic in your WordPress dashboard and adds protection for comments, login, registration and WooCommerce checkout. The free plugin runs locally without an account or API key.&lt;/p&gt;

&lt;p&gt;I’m on the WebDecoy team. This is a practical acceptance test for our &lt;a href="https://wordpress.org/plugins/webdecoy/" rel="noopener noreferrer"&gt;free WordPress plugin&lt;/a&gt;, which runs locally without an account or API key. Start on staging, in monitor mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://webdecoy.com/wordpress/?utm_source=devto&amp;amp;utm_medium=organic_article&amp;amp;utm_campaign=install_webdecoy&amp;amp;utm_content=five_check_guide#wordpress-installer" rel="noopener noreferrer"&gt;Install WebDecoy on your WordPress site&lt;/a&gt;&lt;/strong&gt;. Enter your site URL to open plugin search in your own WordPress admin, then choose &lt;strong&gt;Install Now&lt;/strong&gt; and &lt;strong&gt;Activate&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch the full install, detection and protection walkthrough
&lt;/h2&gt;

&lt;p&gt;This 2:50 walkthrough installs WebDecoy from WordPress.org, inspects the detection log, reviews the default AI crawler policy, and enables protection. In the controlled local test, the exact canary returns HTTP 403 with enforcement, while an ordinary homepage request returns 200. Test requests are labeled; they are not customer attack statistics.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/jc5b1bYyOSo" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webdecoy.com/blog/webdecoy-wordpress-plugin-v2-zero-config-bot-protection/?utm_source=devto&amp;amp;utm_medium=organic_article&amp;amp;utm_campaign=wordpress_install_walkthrough&amp;amp;utm_content=five_check_video" rel="noopener noreferrer"&gt;Full written installation guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Install, then confirm the mode
&lt;/h2&gt;

&lt;p&gt;In wp-admin, open &lt;strong&gt;Plugins → Add New&lt;/strong&gt;, search for &lt;strong&gt;WebDecoy&lt;/strong&gt;, and install and activate it. The plugin author is WebDecoy. You can also use WP-CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp plugin &lt;span class="nb"&gt;install &lt;/span&gt;webdecoy &lt;span class="nt"&gt;--activate&lt;/span&gt;
wp webdecoy config &lt;span class="nb"&gt;set &lt;/span&gt;mode monitor
wp webdecoy config list
wp webdecoy status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitor mode records detections without enforcing blocks. Confirm the setting before testing. Keep a note of your existing security plugins and cache configuration so you know what else can affect a result.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Exercise a normal visitor journey
&lt;/h2&gt;

&lt;p&gt;Use a private browser window. Try the WordPress flows your site actually uses: opening a page, submitting a legitimate comment, logging in and registering if registration is enabled.&lt;/p&gt;

&lt;p&gt;On a WooCommerce staging store, use your payment provider’s sandbox to test checkout. Never use live card transactions to simulate an attack.&lt;/p&gt;

&lt;p&gt;Record whether the action completed, what the visitor saw and whether WebDecoy logged anything. A clean-looking home page does not verify registration or checkout.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Verify a controlled detection
&lt;/h2&gt;

&lt;p&gt;On a staging form protected by WebDecoy, inspect the actual rendered honeypot field. Make a controlled test submission that populates that field, then check the plugin’s local detection log.&lt;/p&gt;

&lt;p&gt;Compare the event time and request path with your test. In monitor mode you are checking observation, not expecting enforcement. If the event is missing, investigate the form integration and request path before assuming protection.&lt;/p&gt;

&lt;p&gt;Use the field your installation actually renders; do not copy a guessed field name. Third-party form builders need their own compatibility check.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Test through the cache and proxy
&lt;/h2&gt;

&lt;p&gt;Repeat a normal page request with the cache cold and warm. Check your host or CDN’s response headers to establish which request was a cache hit.&lt;/p&gt;

&lt;p&gt;A request served entirely by an edge cache may never reach WordPress PHP. Browser-side reporting and server-side inspection are separate paths; verify both. Also verify how the real client address is passed through your trusted proxy before relying on IP-based limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Decide from evidence
&lt;/h2&gt;

&lt;p&gt;Keep a short test record:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;What to record&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Normal visitor&lt;/td&gt;
&lt;td&gt;Completed action and unexpected events&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Controlled honeypot submission&lt;/td&gt;
&lt;td&gt;Matching local detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cache hit and miss&lt;/td&gt;
&lt;td&gt;Which layer observed each request&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sandbox checkout&lt;/td&gt;
&lt;td&gt;Successful normal flow and relevant logs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you have not observed the path you care about, keep investigating in monitor mode. If you later enable enforcement, rerun the legitimate flows immediately. A bot signal is useful evidence; it is not a guarantee that every automated request is malicious.&lt;/p&gt;

&lt;p&gt;The core plugin includes local logs, comment/login/registration protections, honeypot signals and WooCommerce checkout features. Connecting WebDecoy Cloud is optional. You can evaluate local behavior without creating a cloud account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://wordpress.org/plugins/webdecoy/" rel="noopener noreferrer"&gt;Install WebDecoy from WordPress.org&lt;/a&gt;&lt;/strong&gt; and try these checks on one site. If a step behaves differently on your stack, share the WordPress, PHP, form and cache versions and a sanitized description of the result. Do not post visitor IP addresses, credentials or private logs.&lt;/p&gt;

&lt;p&gt;Prepared with AI assistance.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>security</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Catch WordPress bots without a CAPTCHA: a local-first WebDecoy rollout</title>
      <dc:creator>webdecoy</dc:creator>
      <pubDate>Sun, 18 Oct 2026 20:58:06 +0000</pubDate>
      <link>https://dev.to/webdecoy/catch-wordpress-bots-without-a-captcha-a-local-first-webdecoy-rollout-4g4k</link>
      <guid>https://dev.to/webdecoy/catch-wordpress-bots-without-a-captcha-a-local-first-webdecoy-rollout-4g4k</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdrp3tx9elesy7xvys12h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdrp3tx9elesy7xvys12h.png" alt="WebDecoy: Stop bots. Keep customers. WordPress + WooCommerce." width="800" height="259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Spam comments. Fake accounts. A checkout full of failed card attempts. Those are three different places to investigate, but none should require putting an image puzzle in front of every customer.&lt;/p&gt;

&lt;p&gt;We built &lt;strong&gt;WebDecoy&lt;/strong&gt;, a free WordPress.org plugin that combines server-side checks, browser signals and hidden decoys. Local detection works without a WebDecoy account or API key. Here is how to start using it on a site you manage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install and observe
&lt;/h2&gt;

&lt;p&gt;In wp-admin, open &lt;strong&gt;Plugins → Add New&lt;/strong&gt;, search for &lt;strong&gt;WebDecoy&lt;/strong&gt;, install it and activate it. Or use WP-CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp plugin &lt;span class="nb"&gt;install &lt;/span&gt;webdecoy &lt;span class="nt"&gt;--activate&lt;/span&gt;
wp webdecoy config &lt;span class="nb"&gt;set &lt;/span&gt;mode monitor
wp webdecoy status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Monitor mode is the default.&lt;/strong&gt; It records detections without blocking requests, so you can inspect the signals before enabling enforcement. Check the local detection log and statistics rather than assuming that activation means traffic is being blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Match the protection to the problem
&lt;/h2&gt;

&lt;p&gt;For &lt;strong&gt;comment spam and fake registrations&lt;/strong&gt;, the plugin adds invisible honeypot fields to core WordPress forms and evaluates submission signals. Login protection also includes rate limiting for brute-force activity.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;WooCommerce card testing&lt;/strong&gt;, it checks checkout velocity and patterns such as rapid attempts and repeated declines across multiple cards. Checkout limits are configurable, and the plugin supports both classic checkout and WooCommerce Blocks. A hidden decoy coupon adds another signal when automation discovers and applies it.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;scanners and scrapers&lt;/strong&gt;, hidden paths, decoy links, fake-file responses with canary credentials and WordPress-specific traps give automated clients opportunities to reveal their behavior. Browser detection adds automation and behavioral signals when JavaScript executes.&lt;/p&gt;

&lt;p&gt;These mechanisms serve different purposes. A browser signal and a server-side tripwire do not have the same coverage. Review the specific signal behind a detection before choosing your response.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the flows real visitors need
&lt;/h2&gt;

&lt;p&gt;While monitoring, exercise the routes your site actually uses: a normal comment, a registration if enabled, a login, and a test checkout through your payment provider’s supported testing process. Include the devices, privacy tools and accessibility flows your customers use.&lt;/p&gt;

&lt;p&gt;Review the detections alongside those actions. Adjust your settings and allowlist where appropriate, then enable enforcement when the behavior fits your site. Monitor-mode findings are observations, not a measured claim that attacks were blocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful for agencies, too
&lt;/h2&gt;

&lt;p&gt;You can inspect available settings and status through WP-CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp webdecoy config list
wp webdecoy status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The plugin also supports deployment constants for a forced default mode, hiding its admin UI, limiting log retention and an emergency disable switch. That lets you keep the configuration in your deployment process instead of repeating every click across client sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local plugin, optional cloud
&lt;/h2&gt;

&lt;p&gt;Detection logs and statistics work in WordPress. The plugin’s listing documents no external connections until you deliberately connect WebDecoy Cloud. Cloud connection is optional and adds centralized reporting, reputation enrichment and integrations; review its data-sharing details before connecting.&lt;/p&gt;

&lt;p&gt;The plugin runs within WordPress. If you need filtering before requests reach PHP, that requires a separately configured edge or firewall integration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start free
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://wordpress.org/plugins/webdecoy/" rel="noopener noreferrer"&gt;Install WebDecoy from WordPress.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webdecoy.com/wordpress/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=wordpress_local_first" rel="noopener noreferrer"&gt;Open your site’s plugin installer from our WordPress page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.webdecoy.com/sdk-plugins/wordpress-plugin/" rel="noopener noreferrer"&gt;Read the setup documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I’m on the WebDecoy team. If you try it, tell us which signal helps explain the noise on your site: form submissions, scanner traps or checkout activity.&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>security</category>
      <category>woocommerce</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Shopify bot traffic: a practical investigation checklist before you block shoppers</title>
      <dc:creator>webdecoy</dc:creator>
      <pubDate>Sun, 18 Oct 2026 19:32:25 +0000</pubDate>
      <link>https://dev.to/webdecoy/shopify-bot-traffic-a-practical-investigation-checklist-before-you-block-shoppers-3a37</link>
      <guid>https://dev.to/webdecoy/shopify-bot-traffic-a-practical-investigation-checklist-before-you-block-shoppers-3a37</guid>
      <description>&lt;p&gt;A traffic spike with no sales is a reason to investigate. It is not, by itself, proof of bots. The useful question is: &lt;strong&gt;what did those sessions do, and did they connect to an order?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I’m on the WebDecoy team. We built a Shopify app for that investigation. This guide explains the workflow, including the limits that matter before you automate decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Separate three different problems
&lt;/h2&gt;

&lt;p&gt;Treat these as separate questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Suspicious browsing:&lt;/strong&gt; repeated catalog requests, automation signals, or interactions with hidden decoys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checkout abuse:&lt;/strong&gt; bursts of checkout or payment attempts that require payment-provider and Shopify investigation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order review:&lt;/strong&gt; completed orders where you can correlate a storefront session with suspicious activity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An order-review tool cannot retroactively prevent every failed payment attempt. Keep payment controls and detection evidence in their respective roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Verify your collection before interpreting a quiet dashboard
&lt;/h2&gt;

&lt;p&gt;After installing WebDecoy, open your theme editor, go to &lt;strong&gt;App embeds&lt;/strong&gt;, enable &lt;strong&gt;WebDecoy Bot Detection&lt;/strong&gt;, and save. Then open &lt;strong&gt;Setup &amp;amp; health&lt;/strong&gt; in the app.&lt;/p&gt;

&lt;p&gt;Create a labeled storefront test, visit its link, and confirm delivery. Test detections stay out of normal traffic metrics and order review.&lt;/p&gt;

&lt;p&gt;Check storefront delivery and checkout delivery separately. The storefront embed and checkout Web Pixel are different collection paths. A successful storefront test does not prove every checkout event is arriving.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Investigate the signals together
&lt;/h2&gt;

&lt;p&gt;Look for a combination of evidence: browser automation indicators, behavioral patterns, request context, and decoy interactions. IP enrichment adds VPN, proxy, and data-center context.&lt;/p&gt;

&lt;p&gt;A VPN is not a fraud verdict. A cloud IP is not proof that a purchase is unauthorized. Check whether the suspicious activity actually supports the action you are considering.&lt;/p&gt;

&lt;p&gt;WebDecoy’s actor profiles can group related detections across sessions and IPs. That helps investigate repeat activity without treating every request as a completely separate visitor. Correlation is evidence to inspect, not proof of a person’s identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Bring the evidence into Shopify order review
&lt;/h2&gt;

&lt;p&gt;For orders with a matching session and detection, inspect the score, signal breakdown, and available actor history. WebDecoy supports order tags, native Shopify risk assessments, and bot-score and risk-level metafields. Availability depends on your plan and configuration.&lt;/p&gt;

&lt;p&gt;Use the &lt;strong&gt;Flagged Orders&lt;/strong&gt; queue to record whether a reviewed order looks legitimate or suspicious and leave notes for the next person. A useful review record explains why you reached a decision.&lt;/p&gt;

&lt;p&gt;The order-created handler does not independently stop checkout or cancel orders. Cancellation requires a separate Shopify Flow action in a workflow you configure and enable.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Automate the review process first
&lt;/h2&gt;

&lt;p&gt;A sensible first workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect a correlated high-risk order.&lt;/li&gt;
&lt;li&gt;Tag it for staff review.&lt;/li&gt;
&lt;li&gt;Notify the responsible team.&lt;/li&gt;
&lt;li&gt;Record the outcome before adding stronger actions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pro and Agency plans include Shopify Flow triggers. The downloadable review and notification templates start inactive so you can inspect their settings before enabling them.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Keep AI crawling and AI referrals separate
&lt;/h2&gt;

&lt;p&gt;An AI crawler fetching product pages and a shopper arriving from an AI assistant answer different business questions. WebDecoy provides separate reports for observed crawler activity and identifiable AI referrals.&lt;/p&gt;

&lt;p&gt;Referral counts are a floor: an assistant or browser can omit referral information. Robots.txt guidance expresses policy to cooperating crawlers; it does not enforce access against an uncooperative scraper.&lt;/p&gt;

&lt;h2&gt;
  
  
  Know the coverage limits
&lt;/h2&gt;

&lt;p&gt;A browser embed cannot observe every scraper that fetches public HTML without running JavaScript or touching a decoy. Checkout pixel delivery can depend on consent and browser conditions. Order correlation needs a matching session and detection.&lt;/p&gt;

&lt;p&gt;Use these limits when interpreting reports. A quiet dashboard is only meaningful after you have verified the collection path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on your store
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://apps.shopify.com/webdecoy-bot-protection" rel="noopener noreferrer"&gt;WebDecoy is available on the Shopify App Store&lt;/a&gt;, with a Free plan. Install it, enable the app embed, and verify your first test detection before evaluating the data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webdecoy.com/shopify/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=shopify_investigation" rel="noopener noreferrer"&gt;See the full Shopify capabilities and setup guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are investigating a traffic spike, which part is hardest to explain: the source of the visits, checkout activity, or the connection to orders? Please keep customer and payment information out of public comments.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>security</category>
      <category>ecommerce</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>We audited 193 pages with our open-source GEO MCP. Here is what it found.</title>
      <dc:creator>webdecoy</dc:creator>
      <pubDate>Sun, 18 Oct 2026 03:12:10 +0000</pubDate>
      <link>https://dev.to/webdecoy/we-audited-193-pages-with-our-open-source-geo-mcp-here-is-what-it-found-3k2k</link>
      <guid>https://dev.to/webdecoy/we-audited-193-pages-with-our-open-source-geo-mcp-here-is-what-it-found-3k2k</guid>
      <description>&lt;p&gt;We built &lt;strong&gt;GEOMCP&lt;/strong&gt;, a free MIT-licensed MCP server for technical GEO and SEO checks. I’m on the WebDecoy team.&lt;/p&gt;

&lt;p&gt;Then we ran it against all &lt;strong&gt;193 URLs&lt;/strong&gt; in our production sitemap through a real SDK client and stdio MCP server.&lt;/p&gt;

&lt;h2&gt;
  
  
  What needed attention
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Five articles had unnamed checkboxes generated from Markdown task lists.&lt;/li&gt;
&lt;li&gt;The glossary and bot report skipped a heading level.&lt;/li&gt;
&lt;li&gt;Our Features page linked to AI Analytics with the label “Learn More.”&lt;/li&gt;
&lt;li&gt;Three standalone pages had no contextual same-origin links in their main content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We fixed the issues, deployed the changes, and ran the same MCP on those pages. &lt;strong&gt;All 11 original warnings cleared.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The run also found a bug in our comparison tool: it classified unchanged unknown results as coverage changes. We fixed that and added a regression test.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the score does not tell you
&lt;/h2&gt;

&lt;p&gt;182 pages initially scored 100 on the basic checklist. That does not establish AI citations, perfect accessibility, good Core Web Vitals, or higher conversion. The evidence behind each check is more useful than treating the score as a forecast.&lt;/p&gt;

&lt;p&gt;GEOMCP has five read-only tools: audit a URL, audit supplied HTML offline, inspect robots policies, inspect a sitemap, and compare reports. No API key is required. It inspects server-delivered HTML and does not run page scripts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webdecoy.com/geomcp/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=geomcp_launch" rel="noopener noreferrer"&gt;See the real before/after results, the demo, and installation instructions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/WebDecoy/geomcp" rel="noopener noreferrer"&gt;Source and MIT license&lt;/a&gt;. Feedback and reproducible issues are welcome.&lt;/p&gt;

&lt;p&gt;This write-up was prepared with AI assistance from recorded test output and reviewed against the implementation.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>seo</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A bot-risk score is not an identity: designing order reviews in Shopify</title>
      <dc:creator>webdecoy</dc:creator>
      <pubDate>Tue, 13 Oct 2026 03:57:09 +0000</pubDate>
      <link>https://dev.to/webdecoy/a-bot-risk-score-is-not-an-identity-designing-order-reviews-in-shopify-4dc4</link>
      <guid>https://dev.to/webdecoy/a-bot-risk-score-is-not-an-identity-designing-order-reviews-in-shopify-4dc4</guid>
      <description>&lt;p&gt;A suspicious order arrives. Two visits share an IP address, an actor report shows several IPs, and the dashboard assigns a risk score. What should the reviewer actually conclude?&lt;/p&gt;

&lt;p&gt;I’m Chris, founder of WebDecoy. Two questions on our Shopify launch discussion brought this into focus: what evidence survives a review, and how do you tell missing traffic from missing collection? Here is the distinction we use, including the limits of our current app.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Store observations separately from conclusions
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Observation&lt;/th&gt;
&lt;th&gt;What it does not establish by itself&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Two requests used the same IP&lt;/td&gt;
&lt;td&gt;That the same person made them, or that either request was abusive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A correlated actor used multiple IPs&lt;/td&gt;
&lt;td&gt;The legal identity of a shopper, or fraud on every related order&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An IP was classified as a VPN or proxy&lt;/td&gt;
&lt;td&gt;That the customer intended abuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A dashboard shows zero detections&lt;/td&gt;
&lt;td&gt;That collection is healthy, or that no bots visited&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Shared Wi-Fi and company networks are straightforward reasons to be careful with IP-based conclusions. Correlation is an investigation aid. It needs supporting session or fingerprint evidence, and a reviewer should inspect the connection to the particular order.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A decision log and an evidence snapshot solve different problems
&lt;/h2&gt;

&lt;p&gt;In WebDecoy’s Shopify app, saving a review records the decision, note, staff ID, and timestamp in review history. Updating the current review does not replace those earlier history entries. The UI shows the latest 100 decisions for that order.&lt;/p&gt;

&lt;p&gt;That is &lt;strong&gt;not a frozen snapshot of all detection evidence&lt;/strong&gt;. Detection details and the actor’s current cross-IP information can be fetched separately. Looking at them later is not a guarantee that you are seeing precisely what the reviewer saw earlier.&lt;/p&gt;

&lt;p&gt;For any review tool, label these views explicitly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Decision history:&lt;/strong&gt; what a reviewer recorded and when.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Order-linked evidence:&lt;/strong&gt; which detection or session supported the association.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Current investigation context:&lt;/strong&gt; related activity available now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evidence snapshot:&lt;/strong&gt; a preserved version of the evidence at decision time, if your tool actually implements one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebDecoy currently implements the review history, but not that full evidence snapshot. Do not turn a saved note into a claim of complete historical reproducibility.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Keep concurrent reviews from silently overwriting each other
&lt;/h2&gt;

&lt;p&gt;A useful implementation pattern is an optimistic version check. Our review save path validates the submitted version, updates the current decision only when that version still matches, and inserts the history event in the same database transaction.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;begin transaction
  update current review where version = submitted version
  if no row changed: reject as a conflicting review
  append decision + note + reviewer + timestamp
commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reviewer receives a reload message when another staff member has changed the record. This protects the review workflow from a lost update. It still does not preserve the entire detection screen: that requires a separate evidence-versioning design.&lt;/p&gt;

&lt;p&gt;Keep customer and payment details out of free-text notes. Our review UI asks reviewers to avoid them.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Verify collection before interpreting an empty report
&lt;/h2&gt;

&lt;p&gt;WebDecoy’s &lt;strong&gt;Setup &amp;amp; health&lt;/strong&gt; page separates Storefront and Checkout pixel delivery. Each has a last-success timestamp and a last-error timestamp. A missing timestamp appears as &lt;strong&gt;Not yet observed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For storefront collection:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Enable and save the WebDecoy Bot Detection app embed on the published theme.&lt;/li&gt;
&lt;li&gt;Open Setup &amp;amp; health and choose Create test.&lt;/li&gt;
&lt;li&gt;Open the generated storefront test link, unlocking a password-protected store if needed.&lt;/li&gt;
&lt;li&gt;Return and look for &lt;strong&gt;Test detection received. Storefront collection verified.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The test verifies the storefront path; it does not verify checkout-pixel delivery. Test detections are labeled and excluded from traffic metrics, order review, and enforcement.&lt;/p&gt;

&lt;p&gt;Checkout monitoring is separate. A connected pixel with no events is not proof of failure; event delivery can depend on customer consent. Likewise, the absence of a detection is not proof that every scraper was stopped. A client that neither executes JavaScript nor touches a decoy may not appear in the storefront report.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Make the response a deliberate step
&lt;/h2&gt;

&lt;p&gt;In WebDecoy, saving a review records an assessment. Fulfillment actions remain in Shopify. The app’s order-created handler does not independently cancel an order or block checkout. Cancellation requires a separate merchant-configured and enabled Shopify Flow action.&lt;/p&gt;

&lt;p&gt;The useful workflow is: verify collection, inspect the order-linked signals, record the reasoning, then choose the response.&lt;/p&gt;

&lt;h3&gt;
  
  
  See the workflow
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://webdecoy.com/shopify/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=shopify_review_evidence&amp;amp;utm_content=decision_history" rel="noopener noreferrer"&gt;Watch the short Shopify demo and setup walkthrough&lt;/a&gt;. The demo uses a test store and seeded data, not customer results. &lt;a href="https://apps.shopify.com/webdecoy-bot-protection" rel="noopener noreferrer"&gt;WebDecoy is listed on the Shopify App Store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;AI assisted with drafting; implementation claims were checked against the current app code. This is a workflow example, not a fraud-detection accuracy benchmark.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>security</category>
      <category>database</category>
    </item>
    <item>
      <title>Put a check in front of your model before it runs (WebDecoy AI Protection is in beta)</title>
      <dc:creator>webdecoy</dc:creator>
      <pubDate>Fri, 09 Oct 2026 00:30:07 +0000</pubDate>
      <link>https://dev.to/webdecoy/put-a-check-in-front-of-your-model-before-it-runs-webdecoy-ai-protection-is-in-beta-d63</link>
      <guid>https://dev.to/webdecoy/put-a-check-in-front-of-your-model-before-it-runs-webdecoy-ai-protection-is-in-beta-d63</guid>
      <description>&lt;p&gt;Your AI endpoint is probably the most expensive route in your app. Every request to &lt;code&gt;/api/chat&lt;/code&gt; turns into tokens you pay for, and every MCP tool call can read your database, export records or kick off work in another system.&lt;/p&gt;

&lt;p&gt;Most apps protect these routes the same way they protect everything else: authentication, then a rate limit that counts requests. That works until someone signs up, gets a valid session, and points a script at your chat route. Or until an agent with a perfectly valid token calls your &lt;code&gt;documents.export&lt;/code&gt; tool sixty times in a minute. Request counts treat a one-row search and a full export as the same thing.&lt;/p&gt;

&lt;p&gt;We built &lt;strong&gt;AI Protection&lt;/strong&gt; for that gap, and it is now in public beta.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

&lt;p&gt;AI Protection is a set of server-side SDKs you install in your own backend. They sit inside your route, after your authentication and input validation and immediately before the model or tool runs. The rule is simple: &lt;strong&gt;if a request is denied, the model or tool never starts.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js / Next.js&lt;/strong&gt;, &lt;strong&gt;Go&lt;/strong&gt; and &lt;strong&gt;Python (FastAPI)&lt;/strong&gt; SDKs for request admission, plus an experimental Cloudflare Workers adapter&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;Node MCP adapter&lt;/strong&gt; for Streamable HTTP tool servers&lt;/li&gt;
&lt;li&gt;A dashboard where you see decisions, model attempts, tools and individual callers, and can pause a caller or a tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SDKs are public and Apache-2.0: &lt;a href="https://github.com/WebDecoy/ai-protection" rel="noopener noreferrer"&gt;Node&lt;/a&gt;, &lt;a href="https://github.com/WebDecoy/ai-protection-go" rel="noopener noreferrer"&gt;Go&lt;/a&gt;, &lt;a href="https://github.com/WebDecoy/ai-protection-python" rel="noopener noreferrer"&gt;Python&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protecting a chat route (Node)
&lt;/h2&gt;

&lt;p&gt;Create the instance once in a server-only module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createAIProtection&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@webdecoy/ai-protection&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;protect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createAIProtection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;webdecoyUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WEBDECOY_URL&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;webdecoyKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WEBDECOY_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;propertyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WEBDECOY_PROPERTY_ID&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;subjectSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WEBDECOY_SUBJECT_SECRET&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// random, 32+ characters&lt;/span&gt;
  &lt;span class="na"&gt;scopeId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;support-chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/chat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// a fixed route template, never a raw URL&lt;/span&gt;
  &lt;span class="na"&gt;protectionMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;observe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;resolveClientIP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trustedClientIP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// your ingress decides which IP to trust&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, inside your route, after auth and validation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;protect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;callYourModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;trustedClientIP&lt;/code&gt; and &lt;code&gt;callYourModel&lt;/code&gt; are your code, not SDK exports. The callback returns a normal &lt;code&gt;Response&lt;/code&gt;, and a streaming response passes through unchanged. Start in &lt;code&gt;observe&lt;/code&gt; mode, look at what WebDecoy would have done, and switch to &lt;code&gt;enforce&lt;/code&gt; when you trust it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protecting MCP tools
&lt;/h2&gt;

&lt;p&gt;If your AI feature exposes tools over MCP, the adapter wraps each tool so every call is authenticated, validated and authorized by &lt;strong&gt;your&lt;/strong&gt; code before it runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createProtectedMCPHandler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@webdecoy/ai-protection/mcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createProtectedMCPHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.example.com/mcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authorizationServer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://your-issuer.example.com/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;policyVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;records_v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;verifyAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;records.read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Read an owned record&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;inputSchema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;additionalProperties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;requiredScopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;records:read&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;records&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readForTenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;caller&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details we cared about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tool listing is filtered by scope, and every call checks scopes again. Naming a hidden tool directly does not get around it.&lt;/li&gt;
&lt;li&gt;Tenant comes from your verified caller, never from tool arguments, so a forged &lt;code&gt;tenant&lt;/code&gt; argument is rejected before your code runs.&lt;/li&gt;
&lt;li&gt;Every tool result carries an action ID, and the dashboard can open that exact call.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limits measured in work, not requests
&lt;/h2&gt;

&lt;p&gt;A request counter cannot tell a one-row search from a full export. AI Protection can limit &lt;strong&gt;work&lt;/strong&gt;: each tool declares a conservative maximum, the SDK reserves it across all your replicas before the call, and settles what your code actually did afterwards. Model budgets work the same way, reserving tokens before the provider call and settling confirmed usage.&lt;/p&gt;

&lt;p&gt;You also pick what happens when WebDecoy is unreachable, per control. &lt;strong&gt;Fail open&lt;/strong&gt; keeps serving with degraded coverage. &lt;strong&gt;Fail closed&lt;/strong&gt; keeps a hard limit and refuses with a 503. Your own authorization rules apply either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Checking it without paying for a model
&lt;/h2&gt;

&lt;p&gt;The part we are happiest with is the install doctor. From a checkout of the Node SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/WebDecoy/ai-protection &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;ai-protection &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm ci
node scripts/doctor-mcp.mjs detect /path/to/your/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;detect&lt;/code&gt; reads your project (it never runs your code), tells you whether the layout is supported, and prints the exact setup commands with your files filled in. Each command has a &lt;code&gt;plan&lt;/code&gt; mode that changes nothing, and a &lt;code&gt;rollback&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then &lt;code&gt;doctor-mcp.mjs check plan.json&lt;/code&gt; runs your own synthetic calls against the running route: what should be allowed, what should be forbidden, what should be refused as cross-tenant, and what happens on cancellation. Add an &lt;code&gt;outageRuntime&lt;/code&gt; and it serves a stand-in WebDecoy that returns 503 to everything, so you can see your fail-open and fail-closed choices actually behave. For model routes, &lt;code&gt;checkModelProtection&lt;/code&gt; does the same against a stub provider that counts its calls. No paid model calls anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gets sent, and what does not
&lt;/h2&gt;

&lt;p&gt;The SDKs send request metadata (method, your route template, client IP, User-Agent, header names), pseudonymous caller identifiers hashed with a secret you hold, decisions and outcomes, MCP tool names and schema fingerprints, and token counts for budgets.&lt;/p&gt;

&lt;p&gt;They do &lt;strong&gt;not&lt;/strong&gt; send prompts, model responses, tool arguments, tool results, raw user identities or credentials. The pseudonymous identifiers are correlatable on purpose, so we do not call them anonymous.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is not
&lt;/h2&gt;

&lt;p&gt;This is a beta, so here is the honest list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It does not replace your authentication or authorization.&lt;/strong&gt; It runs after them and relies on them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not prompt-injection or output-safety protection.&lt;/strong&gt; It checks who is calling and how much work they are allowed, not what the prompt says.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We have not published detection accuracy numbers.&lt;/strong&gt; We will when there is real-world data behind them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The MCP adapter is Node only&lt;/strong&gt; for now. Go and Python cover request admission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It requires a code change&lt;/strong&gt; in your backend. There is no proxy you can drop in front.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Product page: &lt;a href="https://webdecoy.com/product/ai-protection/" rel="noopener noreferrer"&gt;webdecoy.com/product/ai-protection&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Setup guide: &lt;a href="https://docs.webdecoy.com/ai-protection/setup/" rel="noopener noreferrer"&gt;docs.webdecoy.com/ai-protection/setup&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Install: &lt;code&gt;npm install @webdecoy/ai-protection&lt;/code&gt;, &lt;code&gt;go get github.com/WebDecoy/ai-protection-go@v0.1.0-beta.0&lt;/code&gt; or &lt;code&gt;pip install webdecoy-ai-protection==0.1.0b1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start in observe mode on a test route, run the doctor, and tell us what breaks. We are especially interested in hearing from anyone running MCP servers in production.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>node</category>
      <category>mcp</category>
    </item>
    <item>
      <title>WebDecoy is now on Shopify: from bot signals to order review</title>
      <dc:creator>webdecoy</dc:creator>
      <pubDate>Sat, 03 Oct 2026 03:45:04 +0000</pubDate>
      <link>https://dev.to/webdecoy/webdecoy-is-now-on-shopify-from-bot-signals-to-order-review-5gc</link>
      <guid>https://dev.to/webdecoy/webdecoy-is-now-on-shopify-from-bot-signals-to-order-review-5gc</guid>
      <description>&lt;p&gt;&lt;strong&gt;WebDecoy is now approved and publicly listed on the Shopify App Store.&lt;/strong&gt; We built the app to connect suspicious storefront activity with the work merchants already do: inspect traffic, investigate repeat activity, and review orders before fulfillment.&lt;/p&gt;

&lt;p&gt;Disclosure: I’m the founder of WebDecoy. This post was prepared with AI assistance and checked against our published Shopify implementation and capability documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  New: full Shopify setup walkthrough
&lt;/h2&gt;

&lt;p&gt;Follow the 2:55 walkthrough from the official listing to an enabled storefront embed, a received test detection, and the order-review screen.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/AI8HBwW6mRY" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Recorded in our development store. The app and embed were already installed. We created a fresh labeled setup test; example orders use seeded QA data. Synthetic narration. The test verifies collection, not real-world detection accuracy.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;Setup &amp;amp; health → Open theme editor&lt;/strong&gt;, enable &lt;strong&gt;WebDecoy Bot Detection&lt;/strong&gt; under App embeds and save. Return, select &lt;strong&gt;Check again&lt;/strong&gt;, then &lt;strong&gt;Create test&lt;/strong&gt;. Open the test link within ten minutes and confirm &lt;strong&gt;Test detection received&lt;/strong&gt;. If the store is password protected, unlock it and reopen the link. Checkout-pixel delivery remains a separate check.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webdecoy.com/blog/webdecoy-shopify-app-store-launch/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=shopify_install_walkthrough#follow-the-shopify-installation-walkthrough" rel="noopener noreferrer"&gt;Full written setup steps&lt;/a&gt; · &lt;a href="https://apps.shopify.com/webdecoy-bot-protection" rel="noopener noreferrer"&gt;Install from the Shopify App Store&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  From a visit to an order review
&lt;/h2&gt;

&lt;p&gt;An IP address alone is a poor reason to reject an order. A VPN is context, not proof of fraud. We combine browser behavior, server-derived request signals, IP enrichment, and interactions with hidden honeypot links and fields to make suspicious activity reviewable.&lt;/p&gt;

&lt;p&gt;The integration has three distinct paths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Storefront:&lt;/strong&gt; a Theme App Extension supplies the app embed. Enable WebDecoy Bot Detection in the theme editor and save; there is no script to paste into theme files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Checkout:&lt;/strong&gt; a separate Web Pixel reports supported checkout-funnel events. The theme embed does not run inside Shopify-hosted checkout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orders:&lt;/strong&gt; the storefront writes a session reference into the cart. When an order is created, matching detections and available actor context can contribute to its risk evidence.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keeping those paths separate matters. A successful storefront test does not prove that checkout-pixel events arrive, and an order without the relevant session data cannot acquire a reliable browsing history just because the app is installed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What merchants can inspect
&lt;/h2&gt;

&lt;p&gt;Detection details show scores and supporting signals. Actor profiles group related activity using available fingerprint and session evidence, helping investigate repeat visits across IPs. These are investigative groupings, not guarantees of one unique person.&lt;/p&gt;

&lt;p&gt;Flagged Orders provides a review queue with status and notes. Supported plans add order tagging, native Shopify risk assessments with facts, and Shopify Flow triggers. The app also supplies order-risk metafields and an order-details extension.&lt;/p&gt;

&lt;p&gt;AI Crawlers and AI Referrals are separate reports: catalog retrieval and a shopper arriving from an AI assistant are different events. Referrals depend on identifiable source information; missing referrers cannot be reconstructed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify collection before enabling a response
&lt;/h2&gt;

&lt;p&gt;After installation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Choose a plan through Shopify and enable the theme app embed.&lt;/li&gt;
&lt;li&gt;Open &lt;strong&gt;Setup &amp;amp; health&lt;/strong&gt;, create a labeled storefront test, visit its link, and confirm that the detection arrived.&lt;/li&gt;
&lt;li&gt;Check checkout-pixel delivery separately, accounting for consent and browser conditions.&lt;/li&gt;
&lt;li&gt;Review detections and correlated orders before adding automation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Test detections are excluded from traffic metrics, order review, and enforcement. Setup checks should not become fake threats in production reports.&lt;/p&gt;

&lt;p&gt;Pro and Agency include Shopify Flow triggers. Our downloadable review and staff-notification templates start inactive when imported. Review the settings and enable them for new events when ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The order-created handler does not independently cancel orders or block checkout.&lt;/strong&gt; Cancellation is a separate Flow action that a merchant must deliberately configure and enable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this can observe
&lt;/h2&gt;

&lt;p&gt;A scraper that neither executes JavaScript nor touches a decoy can fetch public pages without appearing in storefront reports. This app does not deploy a WAF in front of Shopify-hosted checkout. We report the evidence our collection surfaces actually observe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the Shopify app
&lt;/h2&gt;

&lt;p&gt;The Free plan includes &lt;strong&gt;500 detections per month&lt;/strong&gt;. Starter is $59 USD/month, Pro $149, and Agency $449. Starter and Pro currently list 14-day trials; Shopify shows current pricing and trial availability before plan approval.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://webdecoy.com/shopify/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=shopify_launch" rel="noopener noreferrer"&gt;Watch the short demo and see the full setup guide&lt;/a&gt;. The demo uses a test store and seeded data, not customer results.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://apps.shopify.com/webdecoy-bot-protection" rel="noopener noreferrer"&gt;Install WebDecoy from the Shopify App Store&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What evidence would your team need before escalating an order for review?&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>security</category>
      <category>webdev</category>
      <category>ecommerce</category>
    </item>
    <item>
      <title>What's calling your Fastify API?</title>
      <dc:creator>webdecoy</dc:creator>
      <pubDate>Wed, 30 Sep 2026 18:49:00 +0000</pubDate>
      <link>https://dev.to/webdecoy/whats-calling-your-fastify-api-3kfh</link>
      <guid>https://dev.to/webdecoy/whats-calling-your-fastify-api-3kfh</guid>
      <description>&lt;p&gt;A product endpoint can serve your frontend, a customer integration, and a scraper through the same URL. Your page analytics may only show the first of those.&lt;/p&gt;

&lt;p&gt;Let's add request detection to a small Fastify API, watch a decoy request produce a verdict, and connect the results to a dashboard. We'll start in monitor mode so detection does not change the API's responses.&lt;/p&gt;

&lt;p&gt;We build WebDecoy. This example uses its Fastify plugin and was prepared with AI assistance. It is request monitoring, separate from our FCaptcha project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a small API
&lt;/h2&gt;

&lt;p&gt;Use Node.js 22.12 or later. Create a folder and install the versions used here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;fastify-crawler-monitor
&lt;span class="nb"&gt;cd &lt;/span&gt;fastify-crawler-monitor
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-exact&lt;/span&gt; fastify@5.12.5 @webdecoy/fastify@0.18.3 @webdecoy/node@0.18.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;server.mjs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Fastify&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fastify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;webdecoy&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@webdecoy/fastify&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tripwire&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@webdecoy/node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Fastify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;trustProxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WEBDECOY_API_KEY&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webdecoy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;monitor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;honeytoken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;skipPaths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;tripwire&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/.env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addHook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;preHandler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webdecoyDecision&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routeOptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;conclusion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conclusion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tripwire&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deniedBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tripwire&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;dashboardConfigured&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;products&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Field notebook&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/.env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4310&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register WebDecoy before your routes and the hook that reads its decision. Fastify's &lt;a href="https://fastify.dev/docs/latest/Reference/Hooks/" rel="noopener noreferrer"&gt;hook documentation&lt;/a&gt; explains the lifecycle and scope.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/.env&lt;/code&gt; handler returns a fixed 404. It never opens an environment file. We use a path that should not contain public application content to demonstrate a concrete rule.&lt;/p&gt;

&lt;p&gt;Automatic HTML trap injection is disabled here with &lt;code&gt;honeytoken: false&lt;/code&gt;. The sample is a JSON API, and we only need request observation for this walkthrough.&lt;/p&gt;

&lt;h2&gt;
  
  
  See a verdict while the API keeps responding
&lt;/h2&gt;

&lt;p&gt;Start the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node server.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://127.0.0.1:4310/api/products
curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://127.0.0.1:4310/.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The product request returns HTTP 200 and a small catalog. The decoy path returns HTTP 404, while the terminal records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;route: '/.env'
conclusion: 'DENY'
tripwire: true
dashboardConfigured: false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DENY&lt;/code&gt; is the rule verdict. Monitor mode lets the application continue, so the response is still the 404 defined by the handler. Seeing a refusal in the log does not mean the request was blocked.&lt;/p&gt;

&lt;p&gt;The example only logs a route template and a few decision fields. It does not print request headers, query strings, bodies, IP addresses, or API keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put the detections in a dashboard
&lt;/h2&gt;

&lt;p&gt;The local tripwire works without an account. To collect cloud detections, &lt;a href="https://app.webdecoy.com/?utm_source=devto&amp;amp;utm_medium=tutorial&amp;amp;utm_campaign=fastify_crawler_monitor" rel="noopener noreferrer"&gt;create a WebDecoy account&lt;/a&gt; and create an API key for the site you want to observe.&lt;/p&gt;

&lt;p&gt;Put the key in a local &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WEBDECOY_API_KEY=your_api_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;.env&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt;. Keep the key on the server, including when you deploy. It does not belong in the frontend that calls this API.&lt;/p&gt;

&lt;p&gt;Stop and restart the server with the file loaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--env-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.env server.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then send the reserved test request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; &lt;span class="s1"&gt;'WebDecoy-Test/1.0'&lt;/span&gt; http://127.0.0.1:4310/api/products
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;strong&gt;Detections&lt;/strong&gt; in WebDecoy and look for the labeled &lt;strong&gt;Test&lt;/strong&gt; record. The endpoint should still return its catalog. The record verifies reporting; it does not represent an actual AI agent visiting your API.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dashboardConfigured: true&lt;/code&gt; only means the process received a key. It does not prove the key is valid or the report arrived. If the record is missing, check the selected site, the key, outbound connectivity, and reporting errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Move it into your application
&lt;/h2&gt;

&lt;p&gt;Keep your existing authentication, validation, authorization, and handlers. Add the plugin before the routes you want covered rather than replacing your server with this demo.&lt;/p&gt;

&lt;p&gt;The demo binds to loopback and trusts no proxy headers. For deployment, configure trusted proxies for your actual network using the &lt;a href="https://docs.webdecoy.com/sdk-plugins/node-sdk/" rel="noopener noreferrer"&gt;SDK's proxy settings&lt;/a&gt;. Do not trust arbitrary forwarded IP headers. Otherwise the traffic you are reviewing can be attributed to the wrong client.&lt;/p&gt;

&lt;p&gt;This plugin uses &lt;code&gt;preHandler&lt;/code&gt;. Requests rejected earlier in Fastify's lifecycle may never reach it. Likewise, requests served entirely by an upstream CDN do not reach this Node process. Use edge collection or access logs for those parts of the traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Work out which activity matters
&lt;/h2&gt;

&lt;p&gt;After deploying, look at the paths, repeated requests, classification, and supporting signals. A client using a familiar AI crawler name is not automatically that company's crawler. A legitimate customer integration is automated too, which is one reason to observe before enforcing a policy.&lt;/p&gt;

&lt;p&gt;Detections are not a complete request counter. Some requests can be allowed locally without cloud reporting, and caching can affect reporting frequency. Keep access logs for overall volume.&lt;/p&gt;

&lt;p&gt;Start with one useful question: which automated clients are repeatedly requesting the content this API exposes? Once you can answer that, you have a better basis for deciding what to allow, limit, or investigate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we checked
&lt;/h2&gt;

&lt;p&gt;The example was tested locally on Node.js 26.5.0 with Fastify 5.12.5 and WebDecoy packages 0.18.3. Checks covered the catalog response, the tripwire verdict with its unchanged 404, the skipped health route, and the reserved test request in monitor mode.&lt;/p&gt;

&lt;p&gt;No API key was used in those checks. Cloud delivery and real-crawler identification are not claimed as local test results.&lt;/p&gt;

</description>
      <category>node</category>
      <category>fastify</category>
      <category>tutorial</category>
      <category>security</category>
    </item>
  </channel>
</rss>
