<?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: Almin Zolotic</title>
    <description>The latest articles on DEV Community by Almin Zolotic (@zologic).</description>
    <link>https://dev.to/zologic</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%2F3790384%2Fc55f6bcf-6195-4a20-a695-27480157be6e.jpg</url>
      <title>DEV Community: Almin Zolotic</title>
      <link>https://dev.to/zologic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zologic"/>
    <language>en</language>
    <item>
      <title>The WooCommerce Cart Abuse Problem Hidden by Modern Bot Traffic</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Sun, 11 Oct 2026 03:04:48 +0000</pubDate>
      <link>https://dev.to/zologic/the-woocommerce-cart-abuse-problem-hidden-by-modern-bot-traffic-2pkh</link>
      <guid>https://dev.to/zologic/the-woocommerce-cart-abuse-problem-hidden-by-modern-bot-traffic-2pkh</guid>
      <description>&lt;p&gt;Residential proxy networks have become a significant part of the infrastructure used for automated internet activity. They can route requests through consumer connections, rotate addresses rapidly, and make traffic difficult to classify using IP reputation or request frequency alone. This is no longer a hypothetical security concern. In July 2026, Lumen's Black Lotus Labs reported tracking nearly &lt;strong&gt;20 million distinct IP addresses per day across more than 30 malicious proxy botnet clusters&lt;/strong&gt;, with more than 20 clusters regularly exceeding 100,000 daily victims. Its research describes a market in which proxy operators can replenish their inventory through interconnected botnets and reselling arrangements. [1]&lt;/p&gt;

&lt;p&gt;The same month, Reuters reported that Google, working with the FBI, Lumen and other partners, had disrupted infrastructure associated with &lt;strong&gt;NetNut&lt;/strong&gt;, a residential proxy service operated by Israeli company Alarum Technologies. Google said the intervention reduced the available proxy-device population by millions. The reported investigation concerned the relationship between NetNut and the &lt;strong&gt;Popa&lt;/strong&gt; botnet. [2] Ynet/Calcalist also reported an FBI investigation into whether devices were enrolled without owners' consent, Alarum's temporary suspension of relevant traffic, and the company's statements that it was cooperating and disputed some outside findings. [3] These reports concern NetNut and do &lt;strong&gt;not&lt;/strong&gt; establish that NetNut was responsible for the traffic described in our WooCommerce investigation.&lt;/p&gt;

&lt;p&gt;Google Threat Intelligence Group published its own technical account on July 2, 2026. It estimated that the NetNut network included &lt;strong&gt;at least 2 million devices worldwide&lt;/strong&gt; and reported observing &lt;strong&gt;316 distinct threat clusters using suspected NetNut exit nodes during one week in June&lt;/strong&gt;, including cybercriminal and espionage activity. Google described SDKs embedded in smart TVs and streaming boxes, disabled Google services used for command and control, and said Play Protect was acting against applications incorporating known NetNut SDKs. These findings are also discussed in Michael Kan’s July 2 PCMag report, &lt;em&gt;This Proxy Service Is Using TV Streaming Devices to Host Cybercrime&lt;/em&gt;. [5][6] The figures describe Google’s investigation, &lt;strong&gt;not the size or attribution of the traffic hitting House of Parfum&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The significance for online stores is practical. A store does not need to be the target of a conventional denial-of-service flood to experience unwanted application work. Distributed automation can invoke legitimate-looking endpoints at scale, while each individual address appears only once.&lt;/p&gt;

&lt;p&gt;During an investigation of automated traffic against &lt;strong&gt;House of Parfum&lt;/strong&gt;, a live WooCommerce store, Zologic observed exactly this pattern. In one correctly parsed server-log interval, we recorded &lt;strong&gt;2,167 direct add-to-cart operations from 2,156 unique source IP addresses&lt;/strong&gt;. Of those operations, &lt;strong&gt;2,139 returned HTTP 403&lt;/strong&gt; after protective controls were introduced. That is approximately &lt;strong&gt;1.005 operations per IP address&lt;/strong&gt;. We built &lt;a href="https://zologic.nl/product/zologic-woocommerce-cart-guard-pro/" rel="noopener noreferrer"&gt;&lt;strong&gt;Zologic WooCommerce Cart Guard&lt;/strong&gt;&lt;/a&gt; to address this specific application-level problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the server was actually processing
&lt;/h2&gt;

&lt;p&gt;The activity centered on WooCommerce's legacy direct add-to-cart mechanism:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;?add-to-cart=&amp;lt;product_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is legitimate WooCommerce functionality. It is also a cart-changing operation that can be requested directly. A syntactically valid URL and an ordinary browser user agent do not establish that a request came through the intended storefront interaction.&lt;/p&gt;

&lt;p&gt;We extracted operations from the &lt;strong&gt;HTTP request field&lt;/strong&gt; of the web-server access logs, rather than searching the entire line for &lt;code&gt;add-to-cart&lt;/code&gt;. That distinction prevents an unrelated referrer URL from being miscounted as a cart operation.&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 result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Actual direct add-to-cart operations&lt;/td&gt;
&lt;td&gt;2,167&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unique source IP addresses&lt;/td&gt;
&lt;td&gt;2,156&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP 403 responses&lt;/td&gt;
&lt;td&gt;2,139&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operations per source IP&lt;/td&gt;
&lt;td&gt;1.005&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The result explains why per-IP rate limiting was poorly matched to this traffic. A rule looking for repeated requests from one address has almost no history to work with when the address changes after approximately one operation. Blocking the first IP can still be useful, but it does not prevent the next request from arriving through a different address.&lt;/p&gt;

&lt;p&gt;This is not evidence that all 2,156 IP addresses belonged to one operator, one proxy provider, or one botnet. It is evidence of a highly distributed pattern of direct WooCommerce operations.&lt;/p&gt;

&lt;h2&gt;
  
  
  What our traffic, ASN and Analytics analysis showed
&lt;/h2&gt;

&lt;p&gt;We investigated several layers of data: raw access-log requests and response codes, source IP addresses, ASN/network ownership, geographic distribution, user-agent strings, Google Analytics observations, and later Cart Guard telemetry.&lt;/p&gt;

&lt;p&gt;The traffic included rotating addresses, ordinary-looking Chrome user agents, and source networks associated with multiple geographies, including Chinese and Singaporean address space. Some infrastructure was consistent with proxy use. &lt;strong&gt;An IP's geolocation is not the location or nationality of the person directing the traffic&lt;/strong&gt;, and an ASN alone does not establish who initiated a request.&lt;/p&gt;

&lt;p&gt;We also escalated traffic involving HostRoyale infrastructure. In its response, HostRoyale identified &lt;strong&gt;Bright Data Ltd. as its customer&lt;/strong&gt; for the reported infrastructure and stated that it had asked Bright Data to investigate its user. That is a specific provider relationship documented by the correspondence; it is not proof that Bright Data itself directed the requests, nor that every source address in the broader dataset was part of its network.&lt;/p&gt;

&lt;p&gt;Google Analytics gave us a different perspective from the origin logs. An HTTP operation may be processed by WooCommerce without appearing as a meaningful Analytics shopping session. This makes Analytics alone an incomplete measurement of automated application work. We used it alongside server and application telemetry, not as a substitute for them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why residential proxy infrastructure matters
&lt;/h2&gt;

&lt;p&gt;Black Lotus Labs' July 2026 report provides broader context for what we observed. Its researchers describe malicious proxy ecosystems that combine compromised routers, Android devices and other consumer hardware with commercial storefronts and resellers. Some operators reportedly regain capacity quickly after disruptions because the supply chain connects multiple proxy pools. [1]&lt;/p&gt;

&lt;p&gt;That research does not identify the operator of our WooCommerce traffic. It does, however, explain why an attacker or automated client can obtain many unrelated-looking source addresses and why static IP lists are an incomplete answer.&lt;/p&gt;

&lt;p&gt;Google’s primary technical report adds detail beyond the news coverage: it identifies consumer streaming hardware as part of the proxy supply chain, describes how home devices can become exit nodes, and reports the use of suspected NetNut exit nodes by hundreds of distinct threat clusters. Google also warned that disrupted proxy operators may purchase capacity from competitors or resell other networks, complicating attempts to block a single provider. [5] PCMag reported these findings and the FBI domain seizures, including Alarum’s statement that it would cooperate with the investigation. [6]&lt;/p&gt;

&lt;p&gt;The NetNut/Popa reporting is another illustration of the scale and enforcement attention surrounding the proxy market. Reuters described Google's July 2026 disruption and Alarum's cooperation statement. [2] Ynet described allegations being investigated about device enrollment and also reported the company's position and response. [3] Investigations, allegations, observed infrastructure links, and proven wrongdoing are not interchangeable.&lt;/p&gt;

&lt;p&gt;For a merchant, the immediate problem is simpler: the request still reaches an application endpoint, regardless of how many intermediary devices or providers carried it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this activity can be missed
&lt;/h2&gt;

&lt;p&gt;WooCommerce security discussions often focus on compromised accounts, vulnerable plugins, malicious uploads, credential attacks and payment fraud. Direct cart abuse can generate none of those indicators.&lt;/p&gt;

&lt;p&gt;The site may see valid HTTP syntax, a recent Chrome user agent, and a source IP with no previous history. Yet processing a cart mutation can involve PHP execution and WooCommerce application work. Depending on configuration, sessions, cookies, cache and database operations may also be involved. The actual performance impact depends on traffic volume and store configuration; our request counts alone do not quantify lost revenue or resource consumption.&lt;/p&gt;

&lt;p&gt;A firewall may correctly find no known exploit signature. A rate limiter may find no repeat offender. Analytics may not represent the requests as ordinary customer sessions. The store is still receiving attempts to perform a cart operation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Cart Guard changes
&lt;/h2&gt;

&lt;p&gt;Zologic developed &lt;a href="https://zologic.nl/product/zologic-woocommerce-cart-guard-pro/" rel="noopener noreferrer"&gt;&lt;strong&gt;WooCommerce Cart Guard&lt;/strong&gt;&lt;/a&gt; around authorization of the cart operation, rather than a universal classification of the client.&lt;/p&gt;

&lt;p&gt;For the legacy &lt;code&gt;?add-to-cart=&lt;/code&gt; path covered by the current implementation, a normal first-party storefront flow obtains short-lived cryptographic proof. Cart Guard verifies that proof before permitting the protected mutation. A direct request without the required proof can be rejected before the protected WooCommerce operation proceeds.&lt;/p&gt;

&lt;p&gt;The proof is &lt;strong&gt;not a human-verification mechanism&lt;/strong&gt;. It establishes the expected storefront context. Browser automation capable of obtaining and replaying legitimate proof within its validity window is a different threat model, so this should not be described as an absolute barrier to all bots or AI agents.&lt;/p&gt;

&lt;p&gt;The design also preserves explicitly supported machine-commerce interfaces. WooCommerce REST/Store API routes and UCP discovery or agent interfaces are not indiscriminately blocked simply because their callers may be automated. The current protected scope is the legacy direct add-to-cart flow, not every possible WooCommerce cart mutation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production results and telemetry
&lt;/h2&gt;

&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%2Fobppa2x37liyq6nkcmei.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%2Fobppa2x37liyq6nkcmei.png" alt="Google Analytics traffic for the House of Parfum WooCommerce store showing a large increase in automated traffic followed by a sharp decline after Zologic WooCommerce Cart Guard was deployed." width="799" height="368"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A later Cart Guard production snapshot recorded 223 direct cart mutations from 222 unique observed sources, with 162 blocked requests, 59 suspicious/no-proof requests, and 2 validated storefront requests. The near one-to-one relationship between mutations and unique sources remained visible at the application layer. These counters span different operating modes: some suspicious/no-proof requests were recorded during Observation mode, so 162/223 should not be interpreted as an Enforcement-mode block rate.&lt;/p&gt;

&lt;p&gt;The telemetry matters because a single blocked-request number does not describe whether a store is seeing repeated requests from one client or one request each from hundreds of clients. In our case, the latter pattern persisted.&lt;/p&gt;

&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%2F7knx7ls8bp4ke05x4nfj.jpg" 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%2F7knx7ls8bp4ke05x4nfj.jpg" alt="Zologic WooCommerce Cart Guard Pro running in Enforcement mode on the House of Parfum production store, showing 223 direct cart mutations from 222 unique sources, 162 blocked requests, 59 suspicious or no-proof requests, 2 validated storefront requests, and an active Cart Guard Pro license." width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Cloudflare, Wordfence and Fail2ban fit
&lt;/h2&gt;

&lt;p&gt;Cart Guard does not replace network or general WordPress security controls. Edge services such as Cloudflare can provide WAF rules, bot management, challenges, reputation filtering and rate limiting. WordPress security plugins cover many other attack classes. Fail2ban can turn precise server-log events into temporary IP bans, and firewall rules can stop unwanted network ranges before PHP executes.&lt;/p&gt;

&lt;p&gt;Those layers remain valuable. We used multiple layers during this incident.&lt;/p&gt;

&lt;p&gt;The narrower issue is that a generic network control does not inherently know whether a WooCommerce direct cart operation followed the storefront sequence expected by the application. Cart Guard makes that decision with WooCommerce-specific context. A resulting HTTP 403 can then become a reliable signal for an additional layer such as Fail2ban.&lt;/p&gt;

&lt;p&gt;We did not find an existing WordPress security control that addressed this exact one-operation-per-IP, legacy direct-cart authorization problem in the way we needed. That is a statement about our evaluation and design requirements, not a claim that no other plugin or Cloudflare configuration can ever mitigate related traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI agents and the changing definition of automated traffic
&lt;/h2&gt;

&lt;p&gt;AI agents, search crawlers, browser automation, scrapers and abusive clients increasingly share the same transport techniques: ordinary browser identities, JavaScript execution, distributed networks and proxy services. The overlap makes source-based classification an imperfect basis for deciding whether an application operation should proceed.&lt;/p&gt;

&lt;p&gt;At the same time, legitimate machine commerce is developing. Blocking everything that looks automated would interfere with supported integrations and emerging agent protocols. A store needs to decide which operations are permitted and through which interfaces.&lt;/p&gt;

&lt;p&gt;For Zologic, the operational lesson was to keep IP intelligence, ASN analysis, analytics, edge controls and incident response, but to move the &lt;strong&gt;cart-operation authorization decision&lt;/strong&gt; into WooCommerce itself. Cart Guard was developed and tested against the distributed traffic described here, with a deliberately limited and measurable scope.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can purchase the battle-tested &lt;a href="https://zologic.nl/product/zologic-woocommerce-cart-guard-pro/" rel="noopener noreferrer"&gt;Zologic WooCommerce Cart Guard&lt;/a&gt; plugin here.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Lumen Black Lotus Labs&lt;/strong&gt;, &lt;em&gt;Symbiotic parasites: the modern proxy ecosystem&lt;/em&gt;, July 24, 2026. &lt;a href="https://www.lumen.com/blog/en-us/symbiotic-parasites-the-modern-proxy-ecosystem" rel="noopener noreferrer"&gt;https://www.lumen.com/blog/en-us/symbiotic-parasites-the-modern-proxy-ecosystem&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reuters&lt;/strong&gt;, &lt;em&gt;Google disrupts NetNut proxy network used in malware operations&lt;/em&gt;, July 2, 2026. &lt;a href="https://www.reuters.com/business/media-telecom/google-disrupts-netnut-proxy-network-used-malware-operations-2026-07-02/" rel="noopener noreferrer"&gt;https://www.reuters.com/business/media-telecom/google-disrupts-netnut-proxy-network-used-malware-operations-2026-07-02/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ynet / Calcalist&lt;/strong&gt;, &lt;em&gt;Israeli tech firm Alarum crashes after FBI probe into proxy network&lt;/em&gt;, July 5, 2026 (date displayed as 07.05.26). &lt;a href="https://www.ynetnews.com/business/article/syvloyu7me" rel="noopener noreferrer"&gt;https://www.ynetnews.com/business/article/syvloyu7me&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zologic incident investigation&lt;/strong&gt;, House of Parfum production web-server access logs, IP/ASN and geography analysis, Google Analytics review, HostRoyale abuse correspondence, and &lt;a href="https://zologic.nl/product/zologic-woocommerce-cart-guard-pro/" rel="noopener noreferrer"&gt;WooCommerce Cart Guard&lt;/a&gt; telemetry, October 2026. Internal evidence; not publicly linked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Threat Intelligence Group&lt;/strong&gt;, &lt;em&gt;Google’s Continued Disruption of Malicious Residential Proxy Networks&lt;/em&gt;, July 2, 2026. &lt;a href="https://cloud.google.com/blog/topics/threat-intelligence/google-continued-disruption-residential-proxy-networks" rel="noopener noreferrer"&gt;https://cloud.google.com/blog/topics/threat-intelligence/google-continued-disruption-residential-proxy-networks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PCMag / Michael Kan&lt;/strong&gt;, &lt;em&gt;This Proxy Service Is Using TV Streaming Devices to Host Cybercrime&lt;/em&gt;, July 2, 2026 (updated July 7). Report text supplied for this article.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Independent research shows e-commerce is directly in the target set
&lt;/h3&gt;

&lt;p&gt;This is not only a pattern we observed on one WooCommerce store. In June 2026, Synthient published research based on outbound traffic collected from residential-proxy SDKs. Its researchers reported that the majority of the proxy traffic they observed facilitated abuse against financial institutions, advertising networks and &lt;strong&gt;e-commerce platforms&lt;/strong&gt;. The report also argues that the industry's increasing emphasis on AI use cases can obscure the abusive traffic that continues to move through these networks.&lt;/p&gt;

&lt;p&gt;Synthient's dataset is especially relevant to what we measured because its analysis focused heavily on the Popa proxy botnet. The researchers reported observing Popa-connected devices routing traffic for both IPIDEA and NetNut, and described an ecosystem built in significant part from Android applications, TV boxes and routers. For May 2026 alone, Synthient reported approximately &lt;strong&gt;9.2 million unique domains and subdomains&lt;/strong&gt; targeted by residential proxies. Its examples of high-value targets included Walmart, alongside Google and Ticketmaster.&lt;/p&gt;

&lt;p&gt;The defensive conclusion independently reaches the same problem we encountered in WooCommerce: traditional IP reputation alone is insufficient. Synthient explicitly recommends that defenders move beyond traditional IP-risk blocklists and use multiple layers of detection. Our production data shows why this becomes particularly difficult at the application level: when 2,167 cart operations arrive from 2,156 source IPs, there is almost no per-IP history on which a conventional rate limiter can act.&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://synthient.com/research/who-are-the-victims-of-residential-proxies" rel="noopener noreferrer"&gt;Synthient Research — Who Are The Victims of Residential Proxies? (June 9, 2026)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>woocommerce</category>
      <category>showdev</category>
      <category>defence</category>
    </item>
    <item>
      <title>8,096 IPs, One WooCommerce Cart Attack: How We Protected the Cart Without Blocking AI Commerce</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Mon, 05 Oct 2026 22:35:03 +0000</pubDate>
      <link>https://dev.to/zologic/8096-ips-one-woocommerce-cart-attack-how-we-protected-the-cart-without-blocking-ai-commerce-k47</link>
      <guid>https://dev.to/zologic/8096-ips-one-woocommerce-cart-attack-how-we-protected-the-cart-without-blocking-ai-commerce-k47</guid>
      <description>&lt;p&gt;A real-world WooCommerce incident involving rotating IPs, browser-like bot traffic, WordPress lifecycle ordering, signed cart proofs, Redis, and keeping UCP/MCP agent commerce online.&lt;/p&gt;

&lt;p&gt;A WooCommerce store we operate started showing a strange traffic pattern.&lt;/p&gt;

&lt;p&gt;At first glance, it looked like growth.&lt;/p&gt;

&lt;p&gt;Traffic increased sharply. The basket page was receiving unusual attention. Google Analytics showed thousands of new sessions.&lt;/p&gt;

&lt;p&gt;Then we looked closer.&lt;/p&gt;

&lt;p&gt;Engagement was almost zero.&lt;/p&gt;

&lt;p&gt;Revenue from the traffic was zero.&lt;/p&gt;

&lt;p&gt;And thousands of requests were hitting WooCommerce URLs containing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;?add-to-cart=...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turned into an interesting engineering problem because the obvious solutions were also the wrong solutions.&lt;/p&gt;

&lt;p&gt;We could not simply block the IP addresses.&lt;/p&gt;

&lt;p&gt;We could not block the browser User-Agent.&lt;/p&gt;

&lt;p&gt;We did not want to block legitimate crawlers indiscriminately.&lt;/p&gt;

&lt;p&gt;And most importantly, we are actively building infrastructure that allows legitimate AI shopping agents to interact with WooCommerce.&lt;/p&gt;

&lt;p&gt;We needed to stop abusive automation &lt;strong&gt;without breaking agentic commerce&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is how we approached it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first signal: analytics that made no sense
&lt;/h2&gt;

&lt;p&gt;The traffic initially looked impressive.&lt;/p&gt;

&lt;p&gt;Thousands of sessions appeared, with large increases in active and new users.&lt;/p&gt;

&lt;p&gt;But almost none of those sessions behaved like customers.&lt;/p&gt;

&lt;p&gt;We saw extremely low engagement and a disproportionate amount of traffic around the WooCommerce basket.&lt;/p&gt;

&lt;p&gt;Server logs provided the real picture.&lt;/p&gt;

&lt;p&gt;Requests repeatedly looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /shop/example-product/?add-to-cart=1234
GET /basket/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes the second request arrived only one or two seconds after the first.&lt;/p&gt;

&lt;p&gt;The User-Agent often looked completely ordinary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/150.0.0.0 Safari/537.36
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Blocking that User-Agent would have been a bad security decision.&lt;/p&gt;

&lt;p&gt;Chrome 150 is not an identity.&lt;/p&gt;

&lt;p&gt;It is just a string.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then we counted the IP addresses
&lt;/h2&gt;

&lt;p&gt;Across the available logs, we extracted unique IP addresses that used that Chrome/150 signature while requesting &lt;code&gt;add-to-cart&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;8,096 unique IP addresses
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This immediately changed the problem.&lt;/p&gt;

&lt;p&gt;Traditional per-IP rate limiting or Fail2ban-style blocking could still remove individual offenders, but it was no longer an effective primary defense.&lt;/p&gt;

&lt;p&gt;Most addresses appeared only once or twice.&lt;/p&gt;

&lt;p&gt;The network was rotating faster than an IP reputation system could meaningfully respond.&lt;/p&gt;

&lt;p&gt;ASN sampling also showed significant hosting and proxy infrastructure, including traffic associated with several hosting networks.&lt;/p&gt;

&lt;p&gt;But ASN blocking was not the answer either.&lt;/p&gt;

&lt;p&gt;The fundamental problem was not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which IP is malicious?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The better question was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should this request be allowed to perform an expensive state-changing WooCommerce operation?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That distinction became the foundation of the solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known bots were a different problem
&lt;/h2&gt;

&lt;p&gt;Not all of the traffic was pretending to be a browser.&lt;/p&gt;

&lt;p&gt;We also observed recognizable crawlers hitting cart operations, including identifiers such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SERankingBacklinksBot
MJ12bot
Reflectionbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those could be handled through explicit bot detection.&lt;/p&gt;

&lt;p&gt;But during testing we discovered another important issue.&lt;/p&gt;

&lt;p&gt;Correctly detecting a bot does not help if enforcement happens too late in the WordPress lifecycle.&lt;/p&gt;

&lt;p&gt;WooCommerce processes legacy add-to-cart requests during &lt;code&gt;wp_loaded&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our enforcement path was initially occurring later.&lt;/p&gt;

&lt;p&gt;That meant a request could be correctly identified as a bot while WooCommerce had already been given an opportunity to process the cart operation.&lt;/p&gt;

&lt;p&gt;The fix was conceptually simple:&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;'wp_loaded'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;__CLASS__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'observe_request'&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Priority matters.&lt;/p&gt;

&lt;p&gt;WooCommerce's cart processing happens later.&lt;/p&gt;

&lt;p&gt;Security enforcement must happen &lt;strong&gt;before the mutation&lt;/strong&gt;, not after it.&lt;/p&gt;

&lt;p&gt;After correcting the lifecycle ordering, controlled tests produced the expected result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MJ12bot      -&amp;gt; 403
Reflectionbot -&amp;gt; 403
SERanking    -&amp;gt; 403
Normal browser -&amp;gt; normal WooCommerce flow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That solved recognizable bots.&lt;/p&gt;

&lt;p&gt;It did not solve the distributed browser-like traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we refused to block Chrome
&lt;/h2&gt;

&lt;p&gt;It was tempting.&lt;/p&gt;

&lt;p&gt;The suspicious traffic had a remarkably consistent Chrome/150 signature.&lt;/p&gt;

&lt;p&gt;But blocking it globally would have violated one of the basic principles we wanted the system to follow:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A User-Agent is a signal, not authentication.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The same applied to several other tempting rules.&lt;/p&gt;

&lt;p&gt;We did not want to block requests merely because they had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an empty Referer&lt;/li&gt;
&lt;li&gt;a particular country&lt;/li&gt;
&lt;li&gt;a particular Chrome version&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;/basket/&lt;/code&gt; URL&lt;/li&gt;
&lt;li&gt;a hosting-provider IP address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each might be useful as a signal.&lt;/p&gt;

&lt;p&gt;None was strong enough to become the security boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect the state transition instead
&lt;/h2&gt;

&lt;p&gt;We built a separate WooCommerce protection layer around the legacy GET add-to-cart flow.&lt;/p&gt;

&lt;p&gt;The idea is straightforward.&lt;/p&gt;

&lt;p&gt;A client should not be able to arrive from nowhere and immediately perform a legacy cart mutation.&lt;/p&gt;

&lt;p&gt;A normal storefront visitor first establishes first-party context.&lt;/p&gt;

&lt;p&gt;The cart request then carries short-lived proof that this context exists.&lt;/p&gt;

&lt;p&gt;The proof is signed using HMAC:&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="nv"&gt;$issued_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$nonce&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wp_generate_password&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;span class="kc"&gt;false&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="nv"&gt;$payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'v1.'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$issued_at&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'.'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$nonce&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;hash_hmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'sha256'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;wp_salt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'auth'&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;The resulting proof is stored in a secure first-party cookie with properties including:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HttpOnly
Secure
SameSite=Lax
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The proof expires after five minutes.&lt;/p&gt;

&lt;p&gt;Before WooCommerce processes a legacy add-to-cart operation, the guard verifies the signature and age.&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;Legacy ?add-to-cart request
        |
        v
Valid short-lived proof?
      /   \
    no     yes
    |       |
   403      v
        WooCommerce
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This does not attempt to determine whether the client is human.&lt;/p&gt;

&lt;p&gt;That is an important distinction.&lt;/p&gt;

&lt;p&gt;A sufficiently capable automation system can obtain the proof.&lt;/p&gt;

&lt;p&gt;The mechanism instead changes the economics and state requirements of the attack while preventing blind, stateless cart mutations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Page caching introduced another complication
&lt;/h2&gt;

&lt;p&gt;There was a practical WordPress problem.&lt;/p&gt;

&lt;p&gt;A heavily cached storefront page may be served without executing the PHP code that would normally issue a proof cookie.&lt;/p&gt;

&lt;p&gt;So proof issuance cannot depend solely on uncached page execution.&lt;/p&gt;

&lt;p&gt;We exposed a cache-safe proof endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/wp-json/wccg/v1/proof
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A request to that endpoint returns the signed first-party cookie and explicitly disables caching.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"issued"&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="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The storefront can establish the proof while full-page caching remains enabled.&lt;/p&gt;

&lt;p&gt;Again, this is not CAPTCHA.&lt;/p&gt;

&lt;p&gt;It is not proof of humanity.&lt;/p&gt;

&lt;p&gt;It is proof of state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redis gives us the next layer
&lt;/h2&gt;

&lt;p&gt;The store already uses Redis as its WordPress external object cache.&lt;/p&gt;

&lt;p&gt;Rather than creating another Redis connection, the protection layer can use the normal WordPress object-cache API.&lt;/p&gt;

&lt;p&gt;That gives us inexpensive counters for signals such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;global cart mutations per minute
requests with valid proof
requests without proof
rejected cart mutations
proof issuance velocity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That becomes useful if the automation adapts.&lt;/p&gt;

&lt;p&gt;For example, if the bot eventually learns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /wp-json/wccg/v1/proof
GET /?add-to-cart=1234
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then possession of a proof is no longer sufficiently discriminating.&lt;/p&gt;

&lt;p&gt;At that point Redis-backed behavioral and velocity signals can become the next layer.&lt;/p&gt;

&lt;p&gt;Security controls should evolve with the attacker instead of pretending one rule will remain perfect forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  But there was another requirement: do not break AI shopping agents
&lt;/h2&gt;

&lt;p&gt;This is where the project became more interesting.&lt;/p&gt;

&lt;p&gt;At Zologic, we are also building &lt;strong&gt;UCPReady&lt;/strong&gt;, our WooCommerce implementation of the Universal Commerce Protocol.&lt;/p&gt;

&lt;p&gt;The same production store under attack is intentionally exposed to legitimate AI commerce systems.&lt;/p&gt;

&lt;p&gt;Its UCP manifest is available at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/.well-known/ucp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and it exposes agent-commerce transports including:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REST
MCP
Embedded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means a simplistic security solution could have "fixed" the bot problem by breaking the future commerce infrastructure we actually wanted to preserve.&lt;/p&gt;

&lt;p&gt;So the cart protection explicitly exempts the UCP and REST infrastructure.&lt;/p&gt;

&lt;p&gt;The result is two separate trust paths.&lt;/p&gt;

&lt;h3&gt;
  
  
  Legacy browser cart mutation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   |
   v
first-party proof
   |
   v
legacy ?add-to-cart
   |
   v
WooCommerce
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Agentic commerce
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI agent
   |
   v
/.well-known/ucp
   |
   +--&amp;gt; REST
   |
   +--&amp;gt; MCP
   |
   +--&amp;gt; Embedded checkout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They do not need to pretend to be the same thing.&lt;/p&gt;

&lt;p&gt;That separation is important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Independent UCP validation
&lt;/h2&gt;

&lt;p&gt;We did not want to rely only on our own testing.&lt;/p&gt;

&lt;p&gt;UCP Checker independently evaluates UCP storefronts and runs live checks against manifests and declared transports.&lt;/p&gt;

&lt;p&gt;House of Parfum currently receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UCP Status: Verified
Grade: A
Score: 100/100

Agent Discovery:      100
UCP Conformance:      100
Capability Coverage:  100

Transports:
- REST
- MCP
- Embedded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The store is also currently listed among the highest-scoring stores on the UCP Checker leaderboard.&lt;/p&gt;

&lt;p&gt;Earlier testing by UCP Checker was particularly valuable to us because their UCP Playground exposed real interoperability problems that controlled development environments had not.&lt;/p&gt;

&lt;p&gt;That is exactly what standards-based engineering needs: independent implementations testing each other's assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security and agent accessibility are not opposites
&lt;/h2&gt;

&lt;p&gt;There is an emerging mistake I expect many commerce sites to make.&lt;/p&gt;

&lt;p&gt;They will see AI bots, scrapers and abusive automation and respond with increasingly broad blocking rules.&lt;/p&gt;

&lt;p&gt;Eventually legitimate agents will be blocked too.&lt;/p&gt;

&lt;p&gt;The opposite mistake is equally dangerous: exposing everything because "AI agents need access."&lt;/p&gt;

&lt;p&gt;The correct model is more nuanced.&lt;/p&gt;

&lt;p&gt;An AI crawler fetching public discovery metadata is not the same operation as an anonymous client mutating a shopping cart.&lt;/p&gt;

&lt;p&gt;A UCP agent using a defined commerce protocol is not the same thing as a scraper hammering legacy WooCommerce URLs.&lt;/p&gt;

&lt;p&gt;Security policy should reflect those differences.&lt;/p&gt;

&lt;p&gt;Our architecture now looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Internet
                       |
          +------------+-------------+
          |                          |
   Recognized bots              Commerce clients
          |                          |
          v                          v
    Bot policy layer          +------+------+
                              |             |
                         Legacy Web       UCP/API
                              |             |
                              v             v
                       Cart proof       Protocol
                         guard           endpoints
                              |             |
                         WooCommerce &amp;lt;------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Different clients.&lt;/p&gt;

&lt;p&gt;Different capabilities.&lt;/p&gt;

&lt;p&gt;Different trust boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The production result
&lt;/h2&gt;

&lt;p&gt;After enforcement became active, we measured the real traffic again.&lt;/p&gt;

&lt;p&gt;For the post-enforcement sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;All add-to-cart requests: 36

403 blocked: 35
302 allowed: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we isolated the distributed Chrome/150 pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chrome/150 add-to-cart requests: 30

403: 30
302: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;30 out of 30 were rejected.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The rotating IP addresses continued.&lt;/p&gt;

&lt;p&gt;The User-Agent continued.&lt;/p&gt;

&lt;p&gt;The requests continued.&lt;/p&gt;

&lt;p&gt;But changing IP addresses no longer helped because IP identity was no longer the security boundary.&lt;/p&gt;

&lt;p&gt;At the same time, we verified that legitimate UCP infrastructure remained online:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200 /.well-known/ucp

200 /wp-json/ucpready/v1/openapi.json

200 /wp-json/ucpready/v1/mcp-tools.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was the outcome we wanted.&lt;/p&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Block more bots.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Stop unauthorized state mutation while preserving legitimate machine commerce.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;The most useful lesson from this incident was not a particular WordPress hook or HMAC function.&lt;/p&gt;

&lt;p&gt;It was changing the question.&lt;/p&gt;

&lt;p&gt;When an attacker can rotate through thousands of addresses, asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Is this IP a bot?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;has diminishing value.&lt;/p&gt;

&lt;p&gt;Instead ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Has this client established the state and authority required to perform this operation?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That principle extends well beyond WooCommerce.&lt;/p&gt;

&lt;p&gt;Protect the operation.&lt;/p&gt;

&lt;p&gt;Define the trust boundary.&lt;/p&gt;

&lt;p&gt;Make expensive state transitions require context.&lt;/p&gt;

&lt;p&gt;Keep machine-readable public interfaces intentionally accessible.&lt;/p&gt;

&lt;p&gt;And treat legitimate agents as first-class clients instead of forcing them to masquerade as browsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Having the same WooCommerce problem?
&lt;/h2&gt;

&lt;p&gt;We are continuing to develop these ideas at &lt;strong&gt;Zologic&lt;/strong&gt;, including WooCommerce bot protection, agent-aware access controls, UCPReady, MCP/REST commerce integrations, and security architectures that distinguish legitimate AI agents from abusive automation.&lt;/p&gt;

&lt;p&gt;If your WooCommerce store is dealing with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;distributed &lt;code&gt;add-to-cart&lt;/code&gt; abuse&lt;/li&gt;
&lt;li&gt;rotating proxy traffic&lt;/li&gt;
&lt;li&gt;cart/session exhaustion&lt;/li&gt;
&lt;li&gt;bots that evade per-IP rate limits&lt;/li&gt;
&lt;li&gt;AI crawler access conflicts&lt;/li&gt;
&lt;li&gt;UCP implementation&lt;/li&gt;
&lt;li&gt;MCP commerce integration&lt;/li&gt;
&lt;li&gt;securing WooCommerce without blocking legitimate AI agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we are interested in looking at the problem.&lt;/p&gt;

&lt;p&gt;The solution should not be another giant IP blacklist.&lt;/p&gt;

&lt;p&gt;It should fit the way your store actually works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you're seeing similar traffic, contact &lt;a href="https://zologic.nl" rel="noopener noreferrer"&gt;Zologic&lt;/a&gt; and send us a sample of the pattern you're dealing with. We can help investigate it and design the appropriate protection or agent-commerce integration.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article describes a real production incident. IP addresses and infrastructure observations are treated as network evidence only; they do not establish the identity or intent of the underlying operator.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>ai</category>
      <category>security</category>
      <category>woocommerce</category>
    </item>
    <item>
      <title>The Field Every WooCommerce PII Guard Misses: meta_data</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Mon, 07 Sep 2026 21:26:41 +0000</pubDate>
      <link>https://dev.to/zologic/the-field-every-woocommerce-pii-guard-misses-metadata-26n6</link>
      <guid>https://dev.to/zologic/the-field-every-woocommerce-pii-guard-misses-metadata-26n6</guid>
      <description>&lt;p&gt;Every WooCommerce MCP server I looked at protects one thing well: the log file. Almost none of them inspect the payload actually crossing the wire to the model.&lt;/p&gt;

&lt;p&gt;That distinction sounds small until you look at what's sitting inside a real order object.&lt;/p&gt;

&lt;h2&gt;
  
  
  The field that breaks field-level thinking
&lt;/h2&gt;

&lt;p&gt;WooCommerce lets any plugin write arbitrary key/value pairs into &lt;code&gt;meta_data&lt;/code&gt;. That's the whole point of it — it's the extension point every third-party plugin uses. It's also the field no permission system or allowlist can reason about, because the same field holds things like this in the same order, side by side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"meta_data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warehouse_reference"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WC-ZG-2026-008192"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"delivery_instruction"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Call customer at +385..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are &lt;code&gt;meta_data&lt;/code&gt;. Both pass any check that authorizes the &lt;em&gt;field&lt;/em&gt;. One is a warehouse reference your fulfillment team needs to see. The other is a phone number that shouldn't leave the store. A tool-permission system can authorize the order operation. A field allowlist can permit &lt;code&gt;meta_data&lt;/code&gt;. Neither one knows what's actually sitting inside that string, because neither one looks.&lt;/p&gt;

&lt;p&gt;That's the gap I built Privacy Guard to close, and it's worth being precise about what "closing it" actually means, because the temptation with anything PII-related is to oversell.&lt;/p&gt;

&lt;h2&gt;
  
  
  What already exists, and where it stops
&lt;/h2&gt;

&lt;p&gt;I'm not the first person to think about PII near an MCP server, and WooCommerce PII redaction isn't a new idea either. WooCommerce's own MCP plugin redacts PII by default — emails, addresses, phone numbers get masked before anything is written to a log. Several community WooCommerce MCP servers take a different approach and just don't expose customer or order PII at all, advertising themselves as "read-only and safe." AbilityGuard, a general WordPress Abilities monitor, leaves output logging off by default specifically because ability responses might contain sensitive data.&lt;/p&gt;

&lt;p&gt;Notice the pattern. Every one of those protects the log, or avoids the field entirely. None of them look at the value crossing the boundary and make a decision about &lt;em&gt;that specific value&lt;/em&gt;. That's true outside WordPress too — even general-purpose MCP gateway middleware I checked runs PII filtering request-side only, with response-side scanning explicitly flagged as unfinished work.&lt;/p&gt;

&lt;p&gt;So the honest claim isn't "nobody has thought about this." It's narrower and stronger: existing controls are aimed at logs, credentials, and tool authorization. Almost nothing is aimed at the live MCP payload itself, at the field level, including inside a container field like &lt;code&gt;meta_data&lt;/code&gt; where the key tells you nothing about what's inside the value.&lt;/p&gt;

&lt;h2&gt;
  
  
  Block before execution, minimize before response
&lt;/h2&gt;

&lt;p&gt;The architecture is bidirectional, not a single sanitize-on-the-way-out step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI
 ↓
MCP request
 ↓
PII policy   ← block unexpected sensitive input before the Ability runs
 ↓
Ability execution
 ↓
PII policy   ← redact / tokenize / omit sensitive output
 ↓
MCP response
 ↓
AI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pre-execution half matters as much as the post-execution half. An Ability the guard doesn't recognize doesn't get a default-allow — it fails closed. That's a stronger guarantee than cleaning the response after the fact, because a sensitive value that never reaches execution can't leak through some code path the redaction step forgot to check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three states, not two
&lt;/h2&gt;

&lt;p&gt;The part I had to get right — and the part I'd get called out on if I got it wrong — is what "pass" actually means. It would be easy to write "detected → block, otherwise → safe" and call it done. That's also a false promise, because no detector, mine or anyone else's, can prove a string contains no PII. It can only fail to find any.&lt;/p&gt;

&lt;p&gt;So there are three states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Detected sensitive             → enforce the configured action
Not permitted by policy        → deny
Permitted + no detector match  → pass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The third state is not a safety certification. It means there's currently no policy or detector basis to stop the value — not that the value is verified clean. Anyone selling the opposite promise is selling something they can't actually back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "measure it" looks like in practice
&lt;/h2&gt;

&lt;p&gt;Talk about detector quality is cheap. I built a WooCommerce-shaped corpus instead — realistic sanitized orders, customers, products, mixed sensitive and benign values inside the same &lt;code&gt;meta_data&lt;/code&gt; structures — and measured it as true positives, false positives, false negatives, true negatives, not pass/fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WOOCOMMERCE_CORPUS  TP=8  FP=0  FN=0  TN=23
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Small baseline, not a claim that WooCommerce PII is a solved problem. But it's a number, and it's checkable, which is more than most "we redact PII" claims in this space offer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that doesn't go stale
&lt;/h2&gt;

&lt;p&gt;Detectors improve. Better phone-number patterns, more identifier formats, maybe a classifier layered on top of the regex later — none of that changes what the product actually is. The enforcement seam is the architecture: a WordPress-native boundary sitting inside the MCP Ability lifecycle, blocking before execution and minimizing before response. The detector is a versioned evidence engine feeding decisions into that seam. You can replace the evidence without replacing the product.&lt;/p&gt;

&lt;p&gt;If you're running WooCommerce data through any MCP client right now, the question worth asking isn't whether your logs are redacted. It's whether anything is looking at the actual value in &lt;code&gt;meta_data&lt;/code&gt; before it leaves your store.&lt;/p&gt;




&lt;p&gt;I build WordPress/WooCommerce infrastructure for agentic commerce at Zologic. If you're running an MCP client against live WooCommerce data and want this enforced rather than assumed, reach out: &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/almin-zolotic/" rel="noopener noreferrer"&gt;Almin Zolotic on LinkedIn&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Further reading&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://woocommerce.com/document/w7s-mcp/" rel="noopener noreferrer"&gt;MCP for WooCommerce Documentation&lt;/a&gt; — official PII redaction in action logs&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://wordpress.org/plugins/ability-guard/" rel="noopener noreferrer"&gt;AbilityGuard – Abilities API Monitor&lt;/a&gt; — output logging disabled by default due to sensitive data risk&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/iOSDevSK/mcp-for-woocommerce" rel="noopener noreferrer"&gt;mcp-for-woocommerce&lt;/a&gt; — community MCP server taking the "no PII exposed" approach&lt;/li&gt;
&lt;/ul&gt;




</description>
      <category>mcp</category>
      <category>wordpress</category>
      <category>ai</category>
      <category>privacy</category>
    </item>
    <item>
      <title>The Inventory Bug Hiding in Every AI-Facing Store (And in Every LLM)</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Tue, 14 Jul 2026 18:38:22 +0000</pubDate>
      <link>https://dev.to/zologic/the-inventory-bug-hiding-in-every-ai-facing-store-and-in-every-llm-2b2p</link>
      <guid>https://dev.to/zologic/the-inventory-bug-hiding-in-every-ai-facing-store-and-in-every-llm-2b2p</guid>
      <description>&lt;p&gt;At Zologic we build &lt;a href="https://zologic.nl/ucpready" rel="noopener noreferrer"&gt;UCPReady&lt;/a&gt;, the WooCommerce implementation of the Universal Commerce Protocol. It's the infrastructure that lets AI agents like Claude, Gemini, and ChatGPT discover, search, and check out on a WooCommerce store autonomously. Most of what we write about is protocol compliance, checkout funnels, agent behavior. This one's different. It's about a bug we found in a customer's store that has nothing to do with AI agents on the surface, and everything to do with them underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  The incident
&lt;/h2&gt;

&lt;p&gt;One of our customers runs a fragrance dropshipping store with a catalog north of &lt;strong&gt;53,000 SKUs&lt;/strong&gt;, synced nightly from a wholesale supplier's API. Standard architecture: pull the vendor feed, match by SKU, update price and stock.&lt;/p&gt;

&lt;p&gt;One night, a customer ordered a product. The order came through, payment cleared, and the product didn't exist anymore. Not out of stock. &lt;em&gt;Deleted upstream.&lt;/em&gt; The wholesaler had quietly removed the SKU weeks earlier after it sat unsold for over a month. The store's sync script had no way of knowing, because it was never built to ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than it looks
&lt;/h2&gt;

&lt;p&gt;Here's the part that made us stop and think harder than a routine sync bug usually warrants: at 53,000+ SKUs, a store like this isn't just serving human shoppers anymore. It's a surface AI agents crawl, index, and reason over, through UCP manifests, MCP tool calls, product schemas. When an agent (or a human, for that matter) asks "is this in stock," the honest answer your system should be able to give is &lt;strong&gt;"as of the last time I confirmed it."&lt;/strong&gt; Most systems don't track that distinction. They track "the last value I stored," full stop.&lt;/p&gt;

&lt;p&gt;That's the bug. Not a missing &lt;code&gt;if&lt;/code&gt; statement. A wrong mental model. The store's database wasn't holding "the inventory." It was holding a cached copy of what the vendor said, last time anyone asked. And a cache with no expiry policy isn't a cache. It's a slowly rotting assumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we found once we fixed it
&lt;/h2&gt;

&lt;p&gt;We rebuilt the sync to track presence, not just values: every SKU the vendor's feed actually returns gets marked as &lt;em&gt;reconfirmed this run&lt;/em&gt;. Anything previously published that's absent from a full feed read gets quarantined, not deleted, just pulled from the storefront, ready to snap back the instant the vendor restocks it.&lt;/p&gt;

&lt;p&gt;Running it for the first time against a catalog that had never had this check before surfaced the scale of the accumulated drift:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;~2,700 products&lt;/strong&gt; (over 11% of the published catalog) had been silently stale, some for months&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;151 new products&lt;/strong&gt; picked up automatically in the same pass&lt;/li&gt;
&lt;li&gt;Total catalog stock units moved from &lt;strong&gt;921,786 to 955,594&lt;/strong&gt; in a single sync: a &lt;strong&gt;+33,808 unit&lt;/strong&gt; correction&lt;/li&gt;
&lt;li&gt;Published stock alone: &lt;strong&gt;676,169 → 698,153&lt;/strong&gt; (+21,984 units)&lt;/li&gt;
&lt;li&gt;Draft-side stock: &lt;strong&gt;245,617 → 257,441&lt;/strong&gt; (+11,824 units)
That's not a rounding error. That's tens of thousands of stock units that were wrong, on a store an AI shopping agent could have been confidently quoting availability from, minutes before check-in.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The paradigm shift
&lt;/h2&gt;

&lt;p&gt;Physical inventory &lt;em&gt;feels&lt;/em&gt; like it should be a stable, durable fact. A bottle either exists in a warehouse or it doesn't. But from your system's point of view, that's never something you observe directly. It's an inference from the last API response you got, and every inference like that has a shelf life whether you model it or not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Anything your system ingests from an external source is perishable by default.&lt;/strong&gt; Freshness has to be actively reconfirmed, never assumed from silence. "The feed didn't mention it" and "the feed confirmed it's fine" have to be different states in your data model. Collapsing them into one is exactly the bug we found.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this converges with everything else we build
&lt;/h2&gt;

&lt;p&gt;This is the same failure shape we deal with constantly on the UCP side. An AI agent operating on stale product data doesn't just annoy a customer. It makes a purchase decision, or tells a shopper a store carries something it doesn't, based on a snapshot nobody re-verified. Multiply that by every WooCommerce store that gets discovered and transacted with autonomously, and "is this data still true, or just still &lt;em&gt;there&lt;/em&gt;" stops being a backend nitpick and becomes a trust question for the entire agentic commerce stack.&lt;/p&gt;

&lt;p&gt;And if you work with LLMs directly, you already know this pattern under a different name. A model's training data is a frozen snapshot. "Who's the CEO," "does this product line still exist," "is this API still current" all fail the exact same way our customer's SKU did: not because the model is wrong, but because &lt;em&gt;nothing tells it its answer has gone stale.&lt;/em&gt; That's why retrieval and web search exist. They reintroduce the "did I just reconfirm this" check that a frozen snapshot structurally can't perform on its own. Even RAG can quietly reintroduce the bug at a smaller scale: an indexed-once, never-recrawled document lets "it's in my vector store" stand in for "it's true," when it only ever meant "it was true whenever I last indexed it."&lt;/p&gt;

&lt;p&gt;Same fix, every time: track &lt;em&gt;last confirmed&lt;/em&gt;, not just &lt;em&gt;last known&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;If your store, or your product, or your model, holds any data it didn't generate itself, ask the unglamorous question up front: what tells you an entry has gone stale, and is "I haven't seen an update" quietly standing in for "confirmed still valid"? At 53,000 SKUs, that gap was worth tens of thousands of stock units and a very real risk of selling something that doesn't exist. At agent scale, it's worse. It's a trust failure nobody notices until an autonomous checkout completes on a product that isn't there.&lt;/p&gt;

&lt;p&gt;Perishable by default. Freshness earned, not assumed. That's the standard we're building UCPReady to hold stores to as AI agents become a bigger share of who's actually reading your catalog.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>automation</category>
      <category>api</category>
      <category>woocommerce</category>
    </item>
    <item>
      <title>The Missing Middleware for Autonomous Agents</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Sun, 05 Jul 2026 13:43:19 +0000</pubDate>
      <link>https://dev.to/zologic/the-missing-middleware-for-autonomous-agents-31mf</link>
      <guid>https://dev.to/zologic/the-missing-middleware-for-autonomous-agents-31mf</guid>
      <description>&lt;h3&gt;
  
  
  How frontier models turned privacy from an application concern into an infrastructure problem
&lt;/h3&gt;

&lt;p&gt;Frontier models faithfully execute instructions. They also faithfully move data across system boundaries. That changes privacy from an application concern into an infrastructure concern — and most agent architectures have not caught up.&lt;/p&gt;

&lt;p&gt;The problem isn't that models misbehave. The problem is that they behave exactly as instructed, which means every piece of data in the context window is a candidate for transmission to the next tool call. In banking, healthcare, legal, HR, and insurance systems, that is not a theoretical risk. It is a structural property of how agents work.&lt;/p&gt;

&lt;p&gt;This post identifies where the risk actually occurs, explains why better models make it worse, and describes the middleware layer that has to exist between the agent and every tool it calls.&lt;/p&gt;

&lt;p&gt;Travel booking is the domain we implemented first. The architecture applies everywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  Better Models, Bigger Problem
&lt;/h2&gt;

&lt;p&gt;Everyone assumes more capable models reduce risk. The opposite is true for data handling.&lt;/p&gt;

&lt;p&gt;GPT-3.5 accidentally protected some secrets because it was inconsistent. It would drop fields, misread nested structures, and fail to extract values cleanly. That unreliability was not a feature, but it occasionally functioned like one.&lt;/p&gt;

&lt;p&gt;Frontier models have eliminated that accident. GPT-4o, Claude Sonnet, and Gemini 1.5 Pro will faithfully extract a passport number from a 3,000-token profile and pass it directly to a seat selection tool that has no use for it. They do this because the data is present and the instruction says "book the flight." The model is not doing anything wrong. That is the problem.&lt;/p&gt;

&lt;p&gt;There are three failure modes, and all three require the model to be working correctly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Over-sharing on tool input.&lt;/strong&gt; An agent receives a full traveler profile to perform a search. The profile contains a passport number. The search tool does not need it. The model passes the whole profile because it was told to call the tool with the traveler's details, and the whole profile is the traveler's details. The passport number now appears in the tool's request log.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Credential leakage in context.&lt;/strong&gt; A tool returns an API response that includes a session token or an authorization header in the body. The model incorporates it into its context. In subsequent tool calls, the model may reference or repeat that token. This is not a hallucination failure. It is a faithful reproduction failure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PII in structured outputs.&lt;/strong&gt; The booking confirmation tool returns a full object containing every passenger detail, payment method, and contact. The agent summarizes it for the user, but the full object is in the context window. If that context is logged, the log contains structured PII at booking confirmation density.&lt;/p&gt;

&lt;p&gt;None of these require a malicious actor. They require normal model behavior operating on data that should have been scoped before the model saw it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture Gap
&lt;/h2&gt;

&lt;p&gt;Traditional systems have a well-understood control plane:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    ↓
API Gateway
    ↓
Service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gateway handles authentication, rate limiting, and policy enforcement. It is not optional. No one ships a production service by having the application call downstream services directly.&lt;/p&gt;

&lt;p&gt;Agentic systems have reproduced the application layer and the service layer but skipped the control plane:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
    ↓
[nothing]
    ↓
Tool Runtime
    ↓
Provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent calls tools directly. Every tool call is a direct transmission of whatever the agent has in context, including data the tool was never intended to receive. The tool returns whatever the provider returns, including data the agent was never intended to see.&lt;/p&gt;

&lt;p&gt;The missing layer is a privacy guard that sits at the tool boundary and enforces policy in both directions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
    ↓
Privacy Guard
    ↓
Tool Runtime
    ↓
Provider
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the same architectural pattern as an Envoy proxy or an API gateway. The implementation is different because the data is structured JSON rather than HTTP traffic, and the policy is semantic rather than header-based. The structural role is identical: a mandatory chokepoint where policy is enforced before data crosses a boundary.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Guard Actually Does
&lt;/h2&gt;

&lt;p&gt;The guard intercepts every tool call in both directions and applies four operations:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scans&lt;/strong&gt; the payload for known PII and secret patterns using deterministic detection: field name matching, regex patterns with validation (Luhn for card numbers, format validation for structured types), and domain-specific rules (booking references, passenger name records, loyalty identifiers in travel; account numbers and routing codes in banking; NPI numbers and diagnosis codes in healthcare).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Decides&lt;/strong&gt; based on configurable policy: allow the data through, redact it with a placeholder, replace it with a deterministic cryptographic token, or block the call entirely. The policy is per-kind and per-severity. Secrets are blocked. High-severity PII is tokenized. Standard PII is redacted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audits&lt;/strong&gt; every inspection, input and output, with a trace ID that correlates the two events. The audit event records what was found and what action was taken, without including the sensitive values or the paths to redacted fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enforces&lt;/strong&gt; by surfacing violations as structured errors the agent runtime can handle, rather than exceptions that crash the call.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Implementation
&lt;/h2&gt;

&lt;p&gt;We built this as a dependency-free Node.js package integrated at the &lt;code&gt;invokeTool()&lt;/code&gt; boundary in the MCP runtime. One instance at startup, shared across all tool calls:&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createPrivacyGuard&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="s2"&gt;@ucp-travel/mcp-privacy-guard&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;travelDetectors&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="s2"&gt;@ucp-travel/mcp-privacy-guard/travel&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;guard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPrivacyGuard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;detectors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;travelDetectors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;tokenizeKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;privacyTokenizeKey&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secret&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;               &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;block&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;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pii&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tokenize&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;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pii&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;                  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;redact&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="na"&gt;audit&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;event&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;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;privacy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&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;Every tool invocation passes through the guard twice:&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;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;invokeTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&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;requestId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Sanitize before the tool sees the payload&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;safePayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runGuard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;guard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tenantId&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Execute the tool on the sanitized payload&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;dispatchToolCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;safePayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Sanitize the response before the agent sees it&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;runGuard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;guard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tenantId&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;The input guard runs before the tool handler receives the payload. The output guard runs before the response reaches the agent's context. Neither the tool nor the agent is modified.&lt;/p&gt;




&lt;h2&gt;
  
  
  Per-Tool Output Allowlists
&lt;/h2&gt;

&lt;p&gt;Not all PII in a tool response is a problem. A booking confirmation is supposed to contain the passenger name and contact email. Redacting them breaks the confirmation flow.&lt;/p&gt;

&lt;p&gt;The solution is per-tool output allowlists that specify which paths are permitted through unredacted:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;OUTPUT_ALLOWLIST_BY_TOOL&lt;/span&gt; &lt;span class="o"&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;MCP_TOOLS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;COMPLETE_CHECKOUT&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="s2"&gt;$.booking.id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$.booking.passengers[*].given_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$.booking.passengers[*].family_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$.booking.contact.email&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;MCP_TOOLS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GET_BOOKING&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="s2"&gt;$.booking.id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$.booking.passengers[*].given_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$.booking.passengers[*].family_name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$.booking.contact.email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$.booking.contact.phone_number&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything not on the allowlist for that tool is redacted or tokenized according to the global policy. The search tool never returns PII even if the provider response includes it. The confirmation tool returns exactly the fields the agent needs.&lt;/p&gt;

&lt;p&gt;The same pattern applies in other domains. A banking agent's account lookup tool might allowlist account nickname and last-four digits. A healthcare agent's appointment tool might allowlist appointment time and provider name. The policy is domain-specific; the mechanism is not.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tokenization and Key Rotation
&lt;/h2&gt;

&lt;p&gt;For high-severity PII, redaction is not enough. You lose the ability to correlate data across audit events. Tokenization replaces the value with a deterministic HMAC-based token tied to a key ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TOKEN:pii/2026-q3:a3f8c2..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token is deterministic: the same input and key produce the same token, so you can correlate across audit events without storing the original value. The key ID is logged in every event that produces or reads a token. When you rotate keys quarterly, old tokens are not orphaned; you know exactly which key was active when they were created.&lt;/p&gt;

&lt;p&gt;Rotation is a two-line environment change. The guard adds under 50ms to any call on payloads below the 200 KB size limit.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Open Question
&lt;/h2&gt;

&lt;p&gt;Tokenization solves the audit correlation problem. It does not solve the agent reasoning problem.&lt;/p&gt;

&lt;p&gt;When the agent receives &lt;code&gt;TOKEN:pii/2026-q3:a3f8c2...&lt;/code&gt; in place of an email address, it cannot send a confirmation to that address. If the agent needs to act on a value rather than acknowledge it, tokenization breaks the flow.&lt;/p&gt;

&lt;p&gt;The current design handles this by allowlisting output paths the agent needs to surface to the user. It does not handle the case where the agent needs to use a value internally across multiple tool calls without seeing the raw value.&lt;/p&gt;

&lt;p&gt;That is a harder problem. Reversible tokenization with a delegated decryption endpoint is one approach. Restricting which tools can receive which token kinds is another. Neither is fully satisfying. We have not solved it yet.&lt;/p&gt;

&lt;p&gt;Most engineering articles end by claiming the problem is solved. This one does not, because it is not. The missing middleware layer exists and is deployable today. The question of how an agent reasons about data it has never seen in plaintext is still open, and the answer will shape how agentic systems handle regulated data at scale.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://www.linkedin.com/in/almin-zolotic/" rel="noopener noreferrer"&gt;Almin Zolotic&lt;/a&gt; is the founder of &lt;a href="https://zologic.nl" rel="noopener noreferrer"&gt;Zologic&lt;/a&gt; and the author of the ucp.travel autonomous booking infrastructure. The architecture described in this article — bidirectional privacy enforcement at the MCP tool invocation boundary, with per-tool allowlists, deterministic tokenization, and domain-specific detection — was developed as part of the ucp.travel infrastructure and is documented here as a reusable pattern for agentic systems handling regulated data.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://ucp.travel" rel="noopener noreferrer"&gt;ucp.travel&lt;/a&gt; is autonomous travel booking infrastructure for operators who want to remove the human agent from the booking loop without losing the compliance properties the human agent currently provides.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>privacy</category>
      <category>traveltech</category>
    </item>
    <item>
      <title>The Missing Layer in Agentic Travel: Why "The AI Can Book It Now" Isn't the Hard Part</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Thu, 18 Jun 2026 16:01:34 +0000</pubDate>
      <link>https://dev.to/zologic/the-missing-layer-in-agentic-travel-why-the-ai-can-book-it-now-isnt-the-hard-part-2l11</link>
      <guid>https://dev.to/zologic/the-missing-layer-in-agentic-travel-why-the-ai-can-book-it-now-isnt-the-hard-part-2l11</guid>
      <description>&lt;p&gt;Something real shifted in commerce this year, and most of the commentary around it is focused on the wrong question.&lt;/p&gt;

&lt;p&gt;Visa recently announced a strategic collaboration with OpenAI to bring secure, identity-verified payments directly into AI-driven shopping experiences. Major payment networks don't make moves like that on a hunch — they make them when a category has crossed from experimental into something worth building real infrastructure around. Agentic commerce, the idea that an AI assistant can discover, decide, and complete a purchase on someone's behalf, is no longer a thought experiment. It's a deployment target.&lt;/p&gt;

&lt;p&gt;The industry conversation around this, though, keeps collapsing into one narrow question: can an AI agent successfully place an order? Increasingly, yes. New protocols for letting agents discover merchants and call their tools are maturing quickly, and "the agent created a cart and checked out" demos are everywhere.&lt;/p&gt;

&lt;p&gt;That's the easy 80%. Travel is where the other 20% lives, and it's the part nobody's marketing slide wants to talk about.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why travel breaks the standard playbook
&lt;/h2&gt;

&lt;p&gt;Most agentic commerce infrastructure today assumes something travel doesn't have: a stable product. A SKU with a fixed price sitting in a catalog, waiting to be added to a cart. That model works beautifully for a t-shirt. It falls apart the moment the "product" is a seat on a specific flight that a human or an agent took eleven minutes to decide on.&lt;/p&gt;

&lt;p&gt;A flight offer isn't a fact. It's a perishable claim with a clock attached. The price quoted at the moment of search can be different by the time checkout happens. The seat itself can simply not be there anymore. None of that is a bug in any individual system — it's how airline inventory has always worked, long before any of this involved an AI. The gap is that most of the emerging agent-commerce tooling was designed around retail's assumptions, not travel's, and there's an open, active conversation in the standards community right now about exactly this: contributors building merchant-facing protocols for AI agents have publicly flagged that the lack of a way to signal "this offer is only valid for the next N minutes" is one of the main reasons the leading agentic commerce standard isn't yet considered production-ready for travel specifically.&lt;/p&gt;

&lt;p&gt;That's not a criticism of the standard. It's a fair description of how hard the problem actually is. Perishability is travel's defining trait, and almost nothing in the current agent-commerce stack was built with it in mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that actually matters: agents aren't naturally careful
&lt;/h2&gt;

&lt;p&gt;Here's the thing that surprised us most while building toward this, and it's the part worth other teams hearing before they find out the hard way.&lt;/p&gt;

&lt;p&gt;It is tempting to assume that a sufficiently capable AI model will just behave responsibly. It'll notice when an offer is about to expire. It'll flag a price that crept up between search and purchase. It'll hesitate before re-booking something that already exists. In practice, none of that can be assumed, and assuming it is where the real risk lives.&lt;/p&gt;

&lt;p&gt;A language model calling a booking tool is making its best judgment call in the moment, based on whatever it's been told and whatever data happens to be in front of it. That's genuinely useful, and modern models are good at it. But "the model usually behaves well" is a completely different claim from "the system cannot be made to overspend, double-book, or silently swap one set of terms for another." The first is a tendency. The second is a guarantee. Only one of those is something you can actually stand behind when real money and a real traveler's itinerary are on the line.&lt;/p&gt;

&lt;p&gt;The practical implication is that every meaningful safety property — a hard budget ceiling, a cabin-class policy, a rule that a flight has to be direct, a requirement that any meaningful airline-initiated schedule change gets a human's eyes on it before anything happens automatically — has to be enforced by the platform sitting between the agent and the booking, not hoped for from whichever model happens to be making the call that day. The agent should be free to reason, suggest, and act quickly. The guardrails need to exist completely independently of whether it chooses to respect them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "safe" actually requires, in plain terms
&lt;/h2&gt;

&lt;p&gt;Once you take that seriously, the requirements stop being abstract and become very concrete.&lt;/p&gt;

&lt;p&gt;Every offer needs an honest expiry, enforced server-side, not just displayed as a courtesy. A price has to be reconfirmed immediately before money moves, not assumed stable from however many minutes earlier it was first quoted. Spending limits, cabin policy, and routing preferences need to be checked against the actual final terms of a booking, not just the terms that looked fine at the moment of search. A booking attempt that looks like a duplicate of something already purchased needs to be caught before a second charge happens, not after. And critically: anything an airline changes after the fact — a schedule shift, a downgraded itinerary — has to surface to an actual human decision, because that's exactly the kind of judgment call that shouldn't be delegated to an agent acting alone, no matter how capable it is.&lt;/p&gt;

&lt;p&gt;None of this is exotic engineering. It's the unglamorous, table-stakes work of building a transactional system that's actually trustworthy, applied to a domain — travel — where the standard agentic commerce tooling hasn't fully caught up yet. Every one of these properties has to be proven, not assumed. The honest way to know a guardrail works is to actively try to break it, watch it hold, and keep a permanent record that it did.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is heading
&lt;/h2&gt;

&lt;p&gt;The standards layer for agentic commerce is moving fast, and that's a good thing — a shared, open way for agents to discover and transact with any business, regardless of platform, is genuinely valuable infrastructure for the whole industry. Large travel technology players are already shipping their own agent-facing interfaces, and that's a healthy sign the category is real, not speculative.&lt;/p&gt;

&lt;p&gt;What's still missing, broadly, is the execution layer underneath travel specifically: the part that takes "an agent can call a booking tool" and turns it into "an agent can be trusted to book within real constraints, with a permanent, auditable record of exactly what it was and wasn't allowed to do." That's the layer we've been building at Zologic, under the name &lt;strong&gt;ucp.travel&lt;/strong&gt; — not a replacement for the emerging standards, but the travel-specific safety and policy infrastructure that sits on top of them, so that the convenience of "just ask the AI to book it" doesn't come at the cost of the control any business or traveler would reasonably expect to keep.&lt;/p&gt;

&lt;p&gt;The easy part of agentic commerce is already here. The trustworthy part is what's actually being built right now — and travel, with its perishable inventory and real financial stakes, is as good a proving ground for that as any.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We're building ucp.travel, the policy and safety execution layer for autonomous travel booking. If you're thinking about similar problems in agentic commerce — perishable inventory, mandate-based spending controls, audit trails for autonomous transactions — we'd like to hear from you, and you're welcome to try ucp.travel directly at &lt;a href="https://ucp.travel" rel="noopener noreferrer"&gt;ucp.travel&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ucp</category>
      <category>ai</category>
      <category>agents</category>
      <category>travel</category>
    </item>
    <item>
      <title>From Booking to Beyond: Making Autonomous Travel Servicing Work in Practice</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Tue, 09 Jun 2026 22:10:28 +0000</pubDate>
      <link>https://dev.to/zologic/from-booking-to-beyond-making-autonomous-travel-servicing-work-in-practice-119h</link>
      <guid>https://dev.to/zologic/from-booking-to-beyond-making-autonomous-travel-servicing-work-in-practice-119h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is part of an ongoing series on building &lt;a href="https://ucp.travel" rel="noopener noreferrer"&gt;ucp.travel&lt;/a&gt; — an agentic travel stack that lets AI agents search, book, change, and cancel flights autonomously. Built by Almin Zolotic at &lt;a href="https://zologic.nl" rel="noopener noreferrer"&gt;Zologic&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Last time I wrote about getting an AI agent to autonomously book a flight: search, revalidate, pay, confirm. That was the exciting part.&lt;/p&gt;

&lt;p&gt;This week was the less glamorous, but arguably more important, part: what happens &lt;em&gt;after&lt;/em&gt; the booking.&lt;/p&gt;

&lt;p&gt;Anyone who has built anything in travel knows the booking is only the beginning. The real complexity shows up afterward. The traveler wants to change their flight. The airline changes it for them. The traveler wants to cancel. Dates shift, prices reprice, confirmation windows expire. A real travel platform has to handle all of this without falling apart — and without making the traveler feel like they are operating blind.&lt;/p&gt;

&lt;p&gt;That is what we spent the week building.&lt;/p&gt;




&lt;h2&gt;
  
  
  The three moments that matter after a booking
&lt;/h2&gt;

&lt;p&gt;There are really three post-booking scenarios that define whether a travel platform feels like a product or a prototype.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voluntary cancellation&lt;/strong&gt; — the traveler decides to cancel and needs to understand the consequence before committing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Airline-initiated change&lt;/strong&gt; — the carrier reschedules or changes the trip, and the platform has to reflect that clearly instead of pretending the booking is still unchanged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voluntary change&lt;/strong&gt; — the traveler wants a different flight, and the system has to guide them through a preview, a confirmation step, and any additional payment required, without ambiguity about what is final and what is not.&lt;/p&gt;

&lt;p&gt;All three are multi-step flows. None should happen in a single call. And none should complete without the traveler knowing exactly what they are confirming.&lt;/p&gt;




&lt;h2&gt;
  
  
  Making irreversible actions feel safe
&lt;/h2&gt;

&lt;p&gt;The principle we kept coming back to was simple: anything irreversible should be preceded by a preview.&lt;/p&gt;

&lt;p&gt;The traveler should see what will happen, have a chance to pause, and only then confirm. If the preview expires before they act, the system should catch that and ask them to start again — rather than proceeding with stale information.&lt;/p&gt;

&lt;p&gt;That sounds obvious, but implementing it correctly takes real discipline. There are a surprising number of places where a system can skip the preview, auto-confirm, or fail to communicate that a confirmation window has closed. We closed those gaps so a preview can never be skipped and a stale confirmation can never slip through.&lt;/p&gt;

&lt;p&gt;The same principle applies to how the system talks about fare conditions. A fare being labelled &lt;em&gt;refundable&lt;/em&gt; is a policy statement — not a guarantee that the system can execute a refund right now. We changed the platform to check live booking state before promising that change or cancellation is available. If a booking cannot be serviced through the current interface, the traveler now hears that clearly instead of getting a confusing error after the fact.&lt;/p&gt;




&lt;h2&gt;
  
  
  Airline-initiated changes were the hardest part
&lt;/h2&gt;

&lt;p&gt;The scenario that took the most care was airline-initiated change.&lt;/p&gt;

&lt;p&gt;When an airline changes a trip, the platform receives an event and has to decide what to do. The wrong answer is to handle it silently. Airline changes can involve new flight times, a different aircraft, or sometimes different routing — none of which the traveler has agreed to.&lt;/p&gt;

&lt;p&gt;The right answer is to surface the change clearly, show what was originally booked versus what is now being proposed, and ask the traveler to review before anything is accepted or rejected.&lt;/p&gt;

&lt;p&gt;We built that escalation path properly, made sure the platform re-fetches fresh booking state before presenting the change, and validated the full flow end to end against real provider behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Honest language matters too
&lt;/h2&gt;

&lt;p&gt;One smaller change that mattered a lot was how cancellations are communicated.&lt;/p&gt;

&lt;p&gt;In many travel products, cancellation language quietly implies the traveler will simply get money back the moment they cancel. That's not always accurate, and saying it anyway is the kind of small dishonesty that erodes trust the first time someone notices the gap.&lt;/p&gt;

&lt;p&gt;So the platform now communicates cancellation in terms of what's actually true: the booking is cancelled, and the traveler is told plainly what happens next with any refund or credit, rather than a vague promise that may not match reality.&lt;/p&gt;

&lt;p&gt;Small wording change. Matters a lot for trust.&lt;/p&gt;




&lt;h2&gt;
  
  
  How we know it actually works
&lt;/h2&gt;

&lt;p&gt;The thing I am most satisfied with this week is not just the feature list.&lt;/p&gt;

&lt;p&gt;It is that we did not stop at &lt;em&gt;"it works locally."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We exercised the full booking lifecycle — booking, changing, cancelling, and handling airline-driven changes — against live provider responses, step by step, until we had confirmed outcomes for every scenario.&lt;/p&gt;

&lt;p&gt;That shifted the project from &lt;em&gt;"we think this works"&lt;/em&gt; to &lt;em&gt;"we know these core flows work."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is a meaningful difference between code that looks correct and a system that has been proven to behave correctly under real conditions. This week we crossed that line for the servicing layer.&lt;/p&gt;




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

&lt;p&gt;The immediate next track is operator tooling: giving the people running travel programs visibility into their bookings and the ability to manage their travelers with confidence.&lt;/p&gt;

&lt;p&gt;The AI agent layer is now in a healthy place.&lt;/p&gt;

&lt;p&gt;Now the work is turning that foundation into a full product for the humans operating on top of it.&lt;/p&gt;

&lt;p&gt;More soon.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Almin Zolotic is the founder of &lt;a href="https://zologic.nl" rel="noopener noreferrer"&gt;Zologic&lt;/a&gt; and building &lt;a href="https://ucp.travel" rel="noopener noreferrer"&gt;ucp.travel&lt;/a&gt; — an agentic travel stack that lets AI agents search, book, change, and cancel flights autonomously. Follow the build here on dev.to.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>travel</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>We Just Completed a Live AI-Agent Flight Booking End to End</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Mon, 01 Jun 2026 14:25:44 +0000</pubDate>
      <link>https://dev.to/zologic/we-just-completed-a-live-ai-agent-flight-booking-end-to-end-3b2</link>
      <guid>https://dev.to/zologic/we-just-completed-a-live-ai-agent-flight-booking-end-to-end-3b2</guid>
      <description>&lt;p&gt;&lt;strong&gt;An AI agent received a natural language instruction, searched for flights, selected an offer, passed authorization checks, and created a confirmed booking — autonomously, without human input after initial setup.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Today that went from architecture spec to production reality.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;At 15:33 CET, on UCPPlayground, we ran the following prompt against our live stack:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Use search_flights for ZAG to AMS tomorrow. Then immediately call complete_checkout on the first direct offer. Do not explain first."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two tool calls. Eight seconds.&lt;/p&gt;

&lt;p&gt;The agent searched. The agent booked. A confirmed booking reference came back. We opened the travel provider dashboard and the order was sitting there — confirmed, with a PNR, an e-ticket, passenger details, everything.&lt;/p&gt;

&lt;p&gt;No human touched the booking flow after the initial traveler setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this is harder than it sounds
&lt;/h2&gt;

&lt;p&gt;Anyone can wrap a travel search API in an MCP tool and call it "AI travel booking." That part is easy.&lt;/p&gt;

&lt;p&gt;The hard part is everything that happens between &lt;em&gt;search&lt;/em&gt; and &lt;em&gt;confirmed booking&lt;/em&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Offers expire.&lt;/strong&gt; Flight fares are perishable inventory. An offer returned from search may be gone in 15–30 minutes. No LLM checks this unprompted — it will try to book an expired offer and get a rejection it doesn't know how to recover from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prices change silently.&lt;/strong&gt; A fare can reprice between the moment the agent sees it and the moment it tries to book. Without a revalidation gate, the agent books at the new price without telling anyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bookings can partially confirm.&lt;/strong&gt; A travel provider can return an async response — booking accepted, outcome pending. An agent that doesn't understand this state will retry, and retrying a booking call risks a double charge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identity and authorization matter.&lt;/strong&gt; Who is this booking for? What are they allowed to spend? What cabin class? Are they direct-only? Without a mandate layer, an agent has no constraints. It will spend whatever inventory is available.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document requirements gate certain routes.&lt;/strong&gt; Some routes require passport details to be validated before an order is created. An agent that doesn't check this wastes a booking attempt and confuses the traveler.&lt;/p&gt;

&lt;p&gt;These aren't edge cases. Every one of them will happen in production. None of them are solved by the travel API or the LLM — they have to be solved by the layer in between.&lt;/p&gt;

&lt;p&gt;That's what we built.&lt;/p&gt;




&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;We built &lt;strong&gt;&lt;a href="https://ucp.travel" rel="noopener noreferrer"&gt;UCP Travel&lt;/a&gt;&lt;/strong&gt; — a travel transaction layer that sits between AI agents and travel inventory.&lt;/p&gt;

&lt;p&gt;It implements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;UCP&lt;/strong&gt; (Universal Commerce Protocol) — the open standard for agentic commerce, including the full checkout lifecycle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP&lt;/strong&gt; (Model Context Protocol) — so any MCP-compatible agent can discover and call our tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AP2&lt;/strong&gt; (Agent Payments Protocol) — cryptographically signed traveler mandates that enforce spending limits, cabin class ceilings, and booking constraints before any transaction
The traveler sets up once: connects their AI assistant via OAuth, signs a mandate defining their booking constraints, stores their travel preferences. After that, every booking is autonomous within those constraints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our capability declarations are public at &lt;code&gt;ucp.travel/.well-known/ucp&lt;/code&gt; and verified by &lt;a href="https://ucpchecker.com/status/ucp.travel" rel="noopener noreferrer"&gt;UCPChecker&lt;/a&gt; — currently the only verified implementation of &lt;code&gt;travel.ucp.mandate&lt;/code&gt; in the world.&lt;/p&gt;

&lt;p&gt;UCPPlayground now recognizes ucp.travel as the reference pattern for travel-as-shopping — the &lt;code&gt;travel.ucp.*&lt;/code&gt; namespace layered over &lt;code&gt;dev.ucp.shopping&lt;/code&gt; is the model the ecosystem is converging on.&lt;/p&gt;




&lt;h2&gt;
  
  
  The UCPPlayground integration
&lt;/h2&gt;

&lt;p&gt;The booking today happened through &lt;strong&gt;UCPPlayground&lt;/strong&gt; — a live testing environment for the UCP ecosystem built by Ben Fisher. It connects real AI models to real UCP endpoints and runs end-to-end agent sessions.&lt;/p&gt;

&lt;p&gt;Gemini 2.5 Flash connected to our MCP endpoint, discovered three tools (&lt;code&gt;search_flights&lt;/code&gt;, &lt;code&gt;complete_checkout&lt;/code&gt;, &lt;code&gt;get_booking&lt;/code&gt;), and completed the booking in 3 turns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Turn 1: search_flights(origin: ZAG, destination: AMS, departure_date: 2026-06-02, max_connections: 0)
Turn 2: complete_checkout(offer_id: off_..., checkout_id: chk_live_1)
Turn 3: [confirmed — PNR returned]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outcome: &lt;code&gt;purchase_completed&lt;/code&gt;. PNR confirmed in the provider dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "traveler setup once" actually means
&lt;/h2&gt;

&lt;p&gt;The autonomous booking only works because we solved the identity problem first.&lt;/p&gt;

&lt;p&gt;During onboarding, a traveler:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creates a profile with travel documents and preferences&lt;/li&gt;
&lt;li&gt;Signs an AP2 Intent Mandate — their authorized booking constraints, cryptographically signed&lt;/li&gt;
&lt;li&gt;Links their AI assistant via OAuth 2.0 Authorization Code flow
From that point, every MCP tool call the agent makes carries a bearer token that resolves server-side to the traveler's identity, mandate, and payment profile. The agent never sees raw credentials, card numbers, or passport data. It just calls tools and gets results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is the architecture that makes autonomous booking trustworthy — not just technically possible.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's live now
&lt;/h2&gt;

&lt;p&gt;The multi-tenant operator platform is running. Operators onboard, configure traveler mandates, and connect AI assistants today. Flight booking is live.&lt;/p&gt;

&lt;p&gt;Expanding next:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stay (hotel) booking alongside flights&lt;/li&gt;
&lt;li&gt;Post-booking servicing — changes, cancellations, ancillaries&lt;/li&gt;
&lt;li&gt;Complete audit trail UI for operators and compliance teams
If you're building an AI travel assistant, a travel management platform, or anything that needs to turn travel intent into confirmed bookings — we'd like to talk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://ucp.travel" rel="noopener noreferrer"&gt;ucp.travel&lt;/a&gt;&lt;/strong&gt; · &lt;a href="mailto:contact@zologic.nl"&gt;contact@zologic.nl&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by Zologic — &lt;a href="https://zologic.nl" rel="noopener noreferrer"&gt;zologic.nl&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;UCP spec: &lt;a href="https://ucp.dev" rel="noopener noreferrer"&gt;ucp.dev&lt;/a&gt; · MCP spec: &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;modelcontextprotocol.io&lt;/a&gt; · AP2: &lt;a href="https://ap2-protocol.org" rel="noopener noreferrer"&gt;ap2-protocol.org&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>travel</category>
      <category>mcp</category>
      <category>ucp</category>
    </item>
    <item>
      <title>30 Days of AI Agents Buying From a Real WooCommerce Store. Here's What the Data Says.</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Tue, 26 May 2026 15:32:55 +0000</pubDate>
      <link>https://dev.to/zologic/30-days-of-ai-agents-buying-from-a-real-woocommerce-store-heres-what-the-data-says-ma0</link>
      <guid>https://dev.to/zologic/30-days-of-ai-agents-buying-from-a-real-woocommerce-store-heres-what-the-data-says-ma0</guid>
      <description>&lt;p&gt;Last week &lt;a class="mentioned-user" href="https://dev.to/benjifisher"&gt;@benjifisher&lt;/a&gt; published a sharp piece on agentic commerce's messy middle — the trust, verification, and liability layer between "found it" and "bought it." His conclusion: the middle is mostly unbuilt.&lt;/p&gt;

&lt;p&gt;We have 30 days of production data that says otherwise. Not a demo. Not a sandbox. A live Dutch perfume store with 40,000 SKUs, real pricing, real tax, real wallet debits. Here is what actually happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;€1,269&lt;/strong&gt; in AI-driven revenue — last 30 days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;39 completed orders&lt;/strong&gt; via AI agents&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;€32.54&lt;/strong&gt; average order value per AI transaction&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;76.5% session conversion&lt;/strong&gt; — 39 of 51 agent sessions resulted in a completed order&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1.5 average calls per session&lt;/strong&gt; — the spec benchmark is 4 (list → create → update → complete)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last number is the one I want to focus on.&lt;/p&gt;

&lt;h2&gt;
  
  
  1.5 calls per session means agents are skipping steps
&lt;/h2&gt;

&lt;p&gt;The UCP spec optimised benchmark is 4 tool calls for a complete purchase: list products, create checkout, update checkout, complete checkout. Our agents are averaging 1.5. &lt;/p&gt;

&lt;p&gt;That is not agents failing — it is agents that have already done discovery in a prior session arriving with intent and going straight to checkout. The &lt;code&gt;/agents.md&lt;/code&gt; instruction layer we shipped two weeks ago is working: agents are reading the operating manual on first visit and not re-reading it on every subsequent transaction.&lt;/p&gt;

&lt;p&gt;The messy middle does not look messy when the protocol is clean and the instructions are explicit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is actually buying
&lt;/h2&gt;

&lt;p&gt;Eight distinct agents hit the store in 30 days. Here is what the agent table shows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Sessions&lt;/th&gt;
&lt;th&gt;Avg Calls&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UCP Directory Verifier&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Discovery only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unknown Agent&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;2.2&lt;/td&gt;
&lt;td&gt;Transacting efficiently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UCP Checker&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1.4&lt;/td&gt;
&lt;td&gt;Validation + light transacting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UCPReady MCP&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1.2&lt;/td&gt;
&lt;td&gt;Internal testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UCP Playground&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;+1 above benchmark&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Clean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ucptools.dev&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;At target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generic Bot&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;At target&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The most interesting row is Unknown Agent — 13 sessions, 2.2 avg calls, at or below target. An agent we cannot identify by name is transacting against the store efficiently and repeatedly. It found the &lt;code&gt;/.well-known/ucp&lt;/code&gt; manifest, parsed the capability declarations, and is completing purchases without any integration work on our side.&lt;/p&gt;

&lt;p&gt;That is the protocol working exactly as designed.&lt;/p&gt;

&lt;p&gt;UCP Playground is the only agent above benchmark at 5 calls — that is Ben's testing environment running full end-to-end validation sessions including the identity linking flow. Expected noise, not a problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the messy middle actually looked like
&lt;/h2&gt;

&lt;p&gt;Building to this point was not clean. The first fully autonomous purchase — WooCommerce order #82251 on March 25 — took 24 hours of debugging across two people. The failures were specific:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotency key formatting.&lt;/strong&gt; Claude kept sending &lt;code&gt;meta&lt;/code&gt; as a stringified JSON object instead of a proper nested object. Fixed at the orchestrator level by auto-injecting the key shape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wallet instrument ID vs handler name.&lt;/strong&gt; The store's validator accepted &lt;code&gt;com.terrawallet.store_credit&lt;/code&gt; (the handler name) but rejected &lt;code&gt;terrawallet-1&lt;/code&gt; (the instrument ID). Two different identifiers, one lookup path, one silent failure mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stale OAuth sessions.&lt;/strong&gt; Merchant-side token revocation was not being detected. Agents were holding expired Bearer tokens and getting 401s with no clear recovery path. Fixed by adding token validation on connect and auto-clearing on 401.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP endpoint override.&lt;/strong&gt; Our Shopify storefront probe was overriding the manifest endpoint. OAuth tokens were stored against &lt;code&gt;/api/mcp&lt;/code&gt; but MCP calls were going to &lt;code&gt;/wp-json/ucpready/v1/mcp&lt;/code&gt;. One endpoint mismatch, zero successful completions until found.&lt;/p&gt;

&lt;p&gt;Each of these was a real failure that blocked real purchases. The protocol did not hide them — the session data captured every one. Observability is what made them fixable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 76.5% conversion means in context
&lt;/h2&gt;

&lt;p&gt;Human checkout conversion on e-commerce averages 2-4%. Mobile is lower. Even high-performing stores rarely exceed 5% on cold traffic.&lt;/p&gt;

&lt;p&gt;76.5% is not comparable to human checkout conversion — agent sessions are intentional by definition, not browsing. But it does tell you something about friction. An agent that reaches &lt;code&gt;create_checkout&lt;/code&gt; on a well-implemented UCP endpoint completes the purchase 76.5% of the time. The failures are mostly discovery sessions (UCP Directory Verifier, UCP Checker) that never intended to purchase.&lt;/p&gt;

&lt;p&gt;Strip out the pure discovery sessions and the completion rate is higher.&lt;/p&gt;

&lt;h2&gt;
  
  
  The small retailer readiness tax — revisited
&lt;/h2&gt;

&lt;p&gt;Paul do Forno at Deloitte posted last week that smaller retailers face a "hidden agentic readiness tax" — perfect catalog data as table stakes, deep system access creating data leakage risk.&lt;/p&gt;

&lt;p&gt;The data above is from a small Dutch retailer. Not a platform. Not an enterprise. A WooCommerce store. The tax is real — it took months of development to get here — but the output is 39 AI-driven orders and €1,269 in revenue in 30 days that would not have existed otherwise.&lt;/p&gt;

&lt;p&gt;The readiness tax is a one-time cost. The revenue is recurring.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;The 13 Unknown Agent sessions are the most interesting data point we have right now. An unidentified agent found this store, parsed the protocol, and is transacting repeatedly. We do not know where it came from. That is the network effect of an open protocol — you do not have to be listed anywhere specific. You publish a conformant &lt;code&gt;/.well-known/ucp&lt;/code&gt; manifest and agents find you.&lt;/p&gt;

&lt;p&gt;The next milestone is identifying that agent and understanding its mandate scope. If it is running AP2 autonomous purchasing without an explicit buyer confirmation step, the audit trail is the only thing standing between "it worked" and "it bought the wrong thing." That is the observability layer Ben is right about — not a theoretical need, a live operational requirement.&lt;/p&gt;

&lt;p&gt;The middle is not unbuilt. It is early, it is messy to get right, and it generates real revenue when you do.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;UCPReady is the WooCommerce plugin behind this data. The AI Agent Analytics dashboard is a Pro feature. houseofparfum.nl is the live reference store — the manifest is at houseofparfum.nl/.well-known/ucp.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you want to see where your WooCommerce store stands: ucpchecker.com&lt;/em&gt;&lt;/p&gt;

</description>
      <category>woocommerce</category>
      <category>ai</category>
      <category>mcp</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Shopify Just Gave AI Agents an Operating Manual. We Built One for WooCommerce.</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Tue, 26 May 2026 15:28:38 +0000</pubDate>
      <link>https://dev.to/zologic/shopify-just-gave-ai-agents-an-operating-manual-we-built-one-for-woocommerce-25m</link>
      <guid>https://dev.to/zologic/shopify-just-gave-ai-agents-an-operating-manual-we-built-one-for-woocommerce-25m</guid>
      <description>&lt;p&gt;When Shopify shipped &lt;code&gt;@shopify/ucp-cli&lt;/code&gt; last week, most of the coverage focused on the obvious part: AI agents can now browse and buy from Shopify stores using the Universal Commerce Protocol.&lt;/p&gt;

&lt;p&gt;What got less attention was the file bundled inside the package — &lt;code&gt;SKILL.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That file is not protocol. It's instructions. It tells AI agents &lt;em&gt;how to behave&lt;/em&gt; when shopping on Shopify: start with discovery, sequence cart before checkout, render totals in the order the merchant provides them, treat escalation as a normal lifecycle step not an error. It encodes operational knowledge that the UCP spec alone doesn't cover.&lt;/p&gt;

&lt;p&gt;Shopify proved something important: &lt;strong&gt;protocol + agent instructions is stronger than protocol alone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We build UCPReady — a WooCommerce plugin that implements the full UCP stack. And until last week, WooCommerce merchants running it had the protocol layer but not the instruction layer. No file telling agents how to work with their store. No guidance surface beyond &lt;code&gt;/.well-known/ucp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We shipped that this week.&lt;/p&gt;




&lt;h2&gt;
  
  
  The gap Shopify exposed
&lt;/h2&gt;

&lt;p&gt;UCP standardizes the transport and schema. Every conformant store — Shopify, WooCommerce, BigCommerce, custom — exposes the same protocol surface. That's the point.&lt;/p&gt;

&lt;p&gt;But Benji Fisher at UCP Checker wrote a sharp breakdown of what Shopify's CLI actually does, and one line stuck:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It can buy from any UCP-compliant store on any platform. It can only &lt;em&gt;find&lt;/em&gt; Shopify stores.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The discovery layer defaults to &lt;code&gt;catalog.shopify.com&lt;/code&gt;. The transaction layer is pure, open UCP. Shopify owns the index; the protocol is shared.&lt;/p&gt;

&lt;p&gt;That asymmetry matters. But there's a second asymmetry that's just as real: Shopify agents arrive at a store already knowing how to behave. Non-Shopify agents arrive and have to figure it out from the profile alone.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;SKILL.md&lt;/code&gt; bundled in a CLI is one way to solve that. But it only works for agents that installed that specific CLI. The more durable fix is publishing the operating manual on the merchant's own domain — discoverable by any agent, from any platform, the moment it finds the store.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we shipped: the Agent Guidance Layer
&lt;/h2&gt;

&lt;p&gt;UCPReady now auto-generates two public documents on every merchant site:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/agents.md&lt;/code&gt;&lt;/strong&gt; — human and agent readable Markdown. Tells any agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with &lt;code&gt;/.well-known/ucp&lt;/code&gt;, use the advertised endpoint, never assume &lt;code&gt;/api/ucp/mcp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;What capabilities this specific merchant supports (cart, checkout, orders, returns, identity linking, embedded checkout)&lt;/li&gt;
&lt;li&gt;How to sequence checkout operations&lt;/li&gt;
&lt;li&gt;What payment handlers are available&lt;/li&gt;
&lt;li&gt;When to stop and escalate to the buyer&lt;/li&gt;
&lt;li&gt;Whether return flows include retention/keep-offer decisions (KeepCard integration)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/llms-full.txt&lt;/code&gt;&lt;/strong&gt; — compact, machine-oriented. Covers protocol version, supported transports (REST, MCP, Embedded), capability inventory, and explicit safe probing guidance.&lt;/p&gt;

&lt;p&gt;Both are generated dynamically from live merchant capabilities. If cart is disabled, the cart section doesn't appear. If KeepCard isn't active, no keep-offer guidance is included. Nothing is hardcoded; nothing claims unsupported behavior.&lt;/p&gt;

&lt;p&gt;Here's what it looks like for a real production store — House of Parfum, currently #1 on ucpchecker.com with a perfect 100/100 score:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# House of Parfum

This merchant uses UCPReady and publishes UCP-compatible shopping
endpoints for AI agents.

## Merchant Profile

- **Store:** House of Parfum
- **Description:** Van iconische parfums tot luxe skincare
- **Discovery:** https://houseofparfum.nl/.well-known/ucp
- **Shop:** https://houseofparfum.nl/shop/

## Start Here

- Discover this merchant from https://houseofparfum.nl/.well-known/ucp
- Use the service endpoint advertised in the profile.
- Do not assume /api/ucp/mcp.
- MCP endpoint: https://houseofparfum.nl/wp-json/ucpready/v1/mcp
- REST endpoint: https://houseofparfum.nl/wp-json/ucpready/v1

## Supported Capabilities

- Catalog browsing and product lookup
- Cart creation and update
- Checkout creation, update, and completion
- Order lookup
- Return flows
- Identity linking
- Embedded checkout

## Checkout Guidance

- Create checkout first, then update it with buyer, fulfillment,
  and payment data as needed.
- Read payment handlers from the live checkout response.
- If checkout reaches a completed state, use the final order as
  the source of truth for totals and discounts.
- Treat coupon failures as recoverable checkout issues, not as
  silent success.

## Returns Guidance

- This merchant supports return tooling.
- Return flows may include optional retention / keep-offer decisions
  when enabled by merchant integrations.
- Ask the buyer before accepting or declining any keep-offer outcome.

## Payment Handlers

- dev.ucp.delegate_payment
- com.terrawallet.store_credit

## Escalate to the Buyer When

- variant or quantity selection is ambiguous
- identity linking is required
- coupon validation fails
- the merchant response requires approval or additional buyer action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can hit it live: &lt;a href="https://houseofparfum.nl/agents.md" rel="noopener noreferrer"&gt;houseofparfum.nl/agents.md&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters more than it might look
&lt;/h2&gt;

&lt;p&gt;When Google announced Universal Cart at I/O 2026, the commerce director described UCP as "a common language that lets agents reach businesses securely and seamlessly." Universal Cart, AI Mode checkout, YouTube Shopping — they all run on it.&lt;/p&gt;

&lt;p&gt;The stores that benefit are the ones agents can actually work with reliably. A broken or ambiguous implementation gets abandoned. A store with a valid manifest, correct capability declarations, and clear operating guidance gets completed transactions.&lt;/p&gt;

&lt;p&gt;Shopify's approach is to bundle instructions in a CLI. That works for Shopify's ecosystem. Our approach is to publish instructions on the merchant's own domain, discoverable by any agent from any platform.&lt;/p&gt;

&lt;p&gt;The instruction layer is not optional anymore. It's part of what makes a store agent-ready.&lt;/p&gt;

&lt;p&gt;That's the WooCommerce answer to what Shopify bundled in their CLI — platform-level agent guidance, open and accessible, not locked to a single discovery surface.&lt;/p&gt;

&lt;p&gt;If you're running WooCommerce and want to see where your store stands: &lt;a href="https://ucpchecker.com" rel="noopener noreferrer"&gt;ucpchecker.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;UCPReady is at &lt;a href="https://zologic.nl/ucpready" rel="noopener noreferrer"&gt;zologic.nl/ucpready&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>woocommerce</category>
      <category>cli</category>
    </item>
    <item>
      <title>Shopify Just Gave AI Agents an Operating Manual. We Built One for WooCommerce.</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Thu, 21 May 2026 22:45:41 +0000</pubDate>
      <link>https://dev.to/zologic/shopify-just-gave-ai-agents-an-operating-manual-we-built-one-for-woocommerce-1p4p</link>
      <guid>https://dev.to/zologic/shopify-just-gave-ai-agents-an-operating-manual-we-built-one-for-woocommerce-1p4p</guid>
      <description>&lt;p&gt;When Shopify shipped &lt;code&gt;@shopify/ucp-cli&lt;/code&gt; last week, most of the coverage focused on the obvious part: AI agents can now browse and buy from Shopify stores using the Universal Commerce Protocol.&lt;/p&gt;

&lt;p&gt;What got less attention was the file bundled inside the package — &lt;code&gt;SKILL.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That file is not protocol. It's instructions. It tells AI agents &lt;em&gt;how to behave&lt;/em&gt; when shopping on Shopify: start with discovery, sequence cart before checkout, render totals in the order the merchant provides them, treat escalation as a normal lifecycle step not an error. It encodes operational knowledge that the UCP spec alone doesn't cover.&lt;/p&gt;

&lt;p&gt;Shopify proved something important: &lt;strong&gt;protocol + agent instructions is stronger than protocol alone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We build UCPReady — a WooCommerce plugin that implements the full UCP stack. And until last week, WooCommerce merchants running it had the protocol layer but not the instruction layer. No file telling agents how to work with their store. No guidance surface beyond &lt;code&gt;/.well-known/ucp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We shipped that this week.&lt;/p&gt;




&lt;h2&gt;
  
  
  The gap Shopify exposed
&lt;/h2&gt;

&lt;p&gt;UCP standardizes the transport and schema. Every conformant store — Shopify, WooCommerce, BigCommerce, custom — exposes the same protocol surface. That's the point.&lt;/p&gt;

&lt;p&gt;But Benji Fisher at UCP Checker wrote a sharp breakdown of what Shopify's CLI actually does, and one line stuck:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It can buy from any UCP-compliant store on any platform. It can only &lt;em&gt;find&lt;/em&gt; Shopify stores.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The discovery layer defaults to &lt;code&gt;catalog.shopify.com&lt;/code&gt;. The transaction layer is pure, open UCP. Shopify owns the index; the protocol is shared.&lt;/p&gt;

&lt;p&gt;That asymmetry matters. But there's a second asymmetry that's just as real: Shopify agents arrive at a store already knowing how to behave. Non-Shopify agents arrive and have to figure it out from the profile alone.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;SKILL.md&lt;/code&gt; bundled in a CLI is one way to solve that. But it only works for agents that installed that specific CLI. The more durable fix is publishing the operating manual on the merchant's own domain — discoverable by any agent, from any platform, the moment it finds the store.&lt;/p&gt;




&lt;h2&gt;
  
  
  What we shipped: the Agent Guidance Layer
&lt;/h2&gt;

&lt;p&gt;UCPReady now auto-generates two public documents on every merchant site:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/agents.md&lt;/code&gt;&lt;/strong&gt; — human and agent readable Markdown. Tells any agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with &lt;code&gt;/.well-known/ucp&lt;/code&gt;, use the advertised endpoint, never assume &lt;code&gt;/api/ucp/mcp&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;What capabilities this specific merchant supports (cart, checkout, orders, returns, identity linking, embedded checkout)&lt;/li&gt;
&lt;li&gt;How to sequence checkout operations&lt;/li&gt;
&lt;li&gt;What payment handlers are available&lt;/li&gt;
&lt;li&gt;When to stop and escalate to the buyer&lt;/li&gt;
&lt;li&gt;Whether return flows include retention/keep-offer decisions (&lt;a href="https://keepcard.io" rel="noopener noreferrer"&gt;KeepCard integration&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/llms-full.txt&lt;/code&gt;&lt;/strong&gt; — compact, machine-oriented. Covers protocol version, supported transports (REST, MCP, Embedded), capability inventory, and explicit safe probing guidance.&lt;/p&gt;

&lt;p&gt;Both are generated dynamically from live merchant capabilities. If cart is disabled, the cart section doesn't appear. If &lt;a href="https://keepcard.io" rel="noopener noreferrer"&gt;KeepCard &lt;/a&gt;isn't active, no keep-offer guidance is included. Nothing is hardcoded; nothing claims unsupported behavior.&lt;/p&gt;

&lt;p&gt;Here's what it looks like for a real production store — House of Parfum, currently #1 on ucpchecker.com with a perfect 100/100 score:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# House of Parfum&lt;/span&gt;

This merchant uses UCPReady and publishes UCP-compatible shopping
endpoints for AI agents.

&lt;span class="gu"&gt;## Merchant Profile&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Store:**&lt;/span&gt; House of Parfum
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Description:**&lt;/span&gt; Van iconische parfums tot luxe skincare
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Discovery:**&lt;/span&gt; https://houseofparfum.nl/.well-known/ucp
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Shop:**&lt;/span&gt; https://houseofparfum.nl/shop/

&lt;span class="gu"&gt;## Start Here&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Discover this merchant from https://houseofparfum.nl/.well-known/ucp
&lt;span class="p"&gt;-&lt;/span&gt; Use the service endpoint advertised in the profile.
&lt;span class="p"&gt;-&lt;/span&gt; Do not assume /api/ucp/mcp.
&lt;span class="p"&gt;-&lt;/span&gt; MCP endpoint: https://houseofparfum.nl/wp-json/ucpready/v1/mcp
&lt;span class="p"&gt;-&lt;/span&gt; REST endpoint: https://houseofparfum.nl/wp-json/ucpready/v1

&lt;span class="gu"&gt;## Supported Capabilities&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Catalog browsing and product lookup
&lt;span class="p"&gt;-&lt;/span&gt; Cart creation and update
&lt;span class="p"&gt;-&lt;/span&gt; Checkout creation, update, and completion
&lt;span class="p"&gt;-&lt;/span&gt; Order lookup
&lt;span class="p"&gt;-&lt;/span&gt; Return flows
&lt;span class="p"&gt;-&lt;/span&gt; Identity linking
&lt;span class="p"&gt;-&lt;/span&gt; Embedded checkout

&lt;span class="gu"&gt;## Checkout Guidance&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Create checkout first, then update it with buyer, fulfillment,
  and payment data as needed.
&lt;span class="p"&gt;-&lt;/span&gt; Read payment handlers from the live checkout response.
&lt;span class="p"&gt;-&lt;/span&gt; If checkout reaches a completed state, use the final order as
  the source of truth for totals and discounts.
&lt;span class="p"&gt;-&lt;/span&gt; Treat coupon failures as recoverable checkout issues, not as
  silent success.

&lt;span class="gu"&gt;## Returns Guidance&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; This merchant supports return tooling.
&lt;span class="p"&gt;-&lt;/span&gt; Return flows may include optional retention / keep-offer decisions
  when enabled by merchant integrations.
&lt;span class="p"&gt;-&lt;/span&gt; Ask the buyer before accepting or declining any keep-offer outcome.

&lt;span class="gu"&gt;## Payment Handlers&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; dev.ucp.delegate_payment
&lt;span class="p"&gt;-&lt;/span&gt; com.terrawallet.store_credit

&lt;span class="gu"&gt;## Escalate to the Buyer When&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; variant or quantity selection is ambiguous
&lt;span class="p"&gt;-&lt;/span&gt; identity linking is required
&lt;span class="p"&gt;-&lt;/span&gt; coupon validation fails
&lt;span class="p"&gt;-&lt;/span&gt; the merchant response requires approval or additional buyer action
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can hit it live: &lt;a href="https://houseofparfum.nl/agents.md" rel="noopener noreferrer"&gt;houseofparfum.nl/agents.md&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters more than it might look
&lt;/h2&gt;

&lt;p&gt;When Google announced Universal Cart at I/O 2026, the commerce director described UCP as "a common language that lets agents reach businesses securely and seamlessly." Universal Cart, AI Mode checkout, YouTube Shopping — they all run on it.&lt;/p&gt;

&lt;p&gt;The stores that benefit are the ones agents can actually work with reliably. A broken or ambiguous implementation gets abandoned. A store with a valid manifest, correct capability declarations, and clear operating guidance gets completed transactions.&lt;/p&gt;

&lt;p&gt;Shopify's approach is to bundle instructions in a CLI. That works for Shopify's ecosystem. Our approach is to publish instructions on the merchant's own domain, discoverable by any agent from any platform.&lt;/p&gt;

&lt;p&gt;The instruction layer is not optional anymore. It's part of what makes a store agent-ready.&lt;/p&gt;

&lt;p&gt;That's the WooCommerce answer to what Shopify bundled in their CLI — platform-level agent guidance, open and accessible, not locked to a single discovery surface.&lt;/p&gt;

&lt;p&gt;If you're running WooCommerce and want to see where your store stands: &lt;a href="https://ucpchecker.com" rel="noopener noreferrer"&gt;ucpchecker.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;UCPReady is at &lt;a href="https://zologic.nl/ucpready" rel="noopener noreferrer"&gt;zologic.nl/ucpready&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>woocommerce</category>
      <category>cli</category>
    </item>
    <item>
      <title>I Replaced a Returns Portal with Five MCP Tools. Here's What Actually Happened.</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Thu, 30 Apr 2026 20:06:06 +0000</pubDate>
      <link>https://dev.to/zologic/i-replaced-a-returns-portal-with-five-mcp-tools-heres-what-actually-happened-43ii</link>
      <guid>https://dev.to/zologic/i-replaced-a-returns-portal-with-five-mcp-tools-heres-what-actually-happened-43ii</guid>
      <description>&lt;p&gt;A live WooCommerce journey where a model searched products, completed a purchase, and handled a return with keep-offer logic — through UCPReady and KeepCard.io on the Universal Commerce Protocol.&lt;/p&gt;

&lt;p&gt;Most "AI commerce" demos are still just chat layered on top of old flows.&lt;/p&gt;

&lt;p&gt;Tonight we did something different.&lt;/p&gt;

&lt;p&gt;We ran a live WooCommerce journey where a model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;searched products&lt;/li&gt;
&lt;li&gt;helped complete a purchase&lt;/li&gt;
&lt;li&gt;and then handled a return with keep-offer logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not on a mock store. Not with human agents behind the curtain. Not with Claude-specific business logic buried in the backend. And not by building "AI features" into the store itself.&lt;/p&gt;

&lt;p&gt;This ran through &lt;strong&gt;UCPReady&lt;/strong&gt; and &lt;strong&gt;KeepCard.io&lt;/strong&gt; — two products I built — using protocol-native rails so the model could interact with the store as an agent.&lt;/p&gt;




&lt;h2&gt;
  
  
  The transcript in one line
&lt;/h2&gt;

&lt;p&gt;This was the actual lifecycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Customer asked for a face cleanser&lt;/li&gt;
&lt;li&gt;Model searched the live WooCommerce catalog&lt;/li&gt;
&lt;li&gt;Customer bought two products&lt;/li&gt;
&lt;li&gt;Checkout ran through UCPReady&lt;/li&gt;
&lt;li&gt;Payment used wallet rails plus browser authorization&lt;/li&gt;
&lt;li&gt;Order completed&lt;/li&gt;
&lt;li&gt;Customer changed their mind about one item&lt;/li&gt;
&lt;li&gt;KeepCard.io verified the order, surfaced the line items, collected the reason, and presented a keep offer&lt;/li&gt;
&lt;li&gt;Customer accepted the offer&lt;/li&gt;
&lt;li&gt;A real discount code was generated&lt;/li&gt;
&lt;li&gt;A real confirmation email was sent&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the entire commerce loop in one conversational journey.&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;The session started like a normal shopping flow. The model searched a live WooCommerce store, found products, built a checkout, selected fulfillment, selected payment, and moved through the purchase flow using UCPReady.&lt;/p&gt;

&lt;p&gt;Then, in the same broader system, we tested returns through KeepCard.io.&lt;/p&gt;

&lt;p&gt;The return flow did this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Looked up the order by order number and email&lt;/li&gt;
&lt;li&gt;Checked eligibility&lt;/li&gt;
&lt;li&gt;Collected the return reason&lt;/li&gt;
&lt;li&gt;Evaluated keep-offer logic&lt;/li&gt;
&lt;li&gt;Presented the customer with a keep offer &lt;strong&gt;in chat&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Accepted the offer&lt;/li&gt;
&lt;li&gt;Generated a real discount code&lt;/li&gt;
&lt;li&gt;Sent the confirmation email&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We saw the discount code issued. We saw the email arrive. We saw the KeepCard dashboard record the return session as &lt;strong&gt;Kept&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is not "AI-assisted support." That is a real agentic commerce lifecycle.&lt;/p&gt;




&lt;h2&gt;
  
  
  A real example from tonight
&lt;/h2&gt;

&lt;p&gt;The customer started with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I want to buy something to clean my face&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The model searched the live catalog and found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rilastil Aqua Face Cleanser 200ml — €16.95&lt;/li&gt;
&lt;li&gt;Shiseido Men Face Cleanser 125ml — €18.95&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The customer then said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;buy me 2 of Rilastil Aqua Face Cleanser 200ml and 1 Shiseido Men Face Cleanser 125ml&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The system created checkout, configured fulfillment and wallet payment, and moved the buyer into the final authorization step.&lt;/p&gt;

&lt;p&gt;After the order completed, the customer came back with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Oh no, I made a mistake — I want to return the item&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The return flow asked for the order number, verified eligibility, and surfaced the exact purchased items:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rilastil Aqua Face Cleanser 200ml (Quantity: 2)&lt;/li&gt;
&lt;li&gt;Shiseido Men Face Cleanser 125ml (Quantity: 1)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The customer selected only the Shiseido item. Then gave the reason:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Other — I made a mistake&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of routing to a standard return portal, the system evaluated the return and offered a keep option:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can keep the Shiseido Men Face Cleanser and receive €1.00 off your next order as a discount code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The customer replied:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Accept&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep offer accepted ✓&lt;/li&gt;
&lt;li&gt;Discount code &lt;strong&gt;KEEP-3V9FNM&lt;/strong&gt; generated in WooCommerce ✓&lt;/li&gt;
&lt;li&gt;Confirmation email delivered via KeepCard's email stack ✓&lt;/li&gt;
&lt;li&gt;No return shipment needed ✓&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is exactly the kind of post-purchase flow that usually lives inside rigid portals, manual support queues, or custom integrations tied to one model vendor. Tonight it happened conversationally on top of a live WooCommerce stack.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architecture: what UCPReady and KeepCard.io actually are
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;UCPReady&lt;/strong&gt; is a WooCommerce plugin that implements the &lt;a href="https://ucp.dev" rel="noopener noreferrer"&gt;Universal Commerce Protocol (UCP)&lt;/a&gt; — an open protocol for exposing store capabilities through structured, machine-operable interfaces. It turns a WooCommerce store into a UCP-compliant endpoint that AI agents can discover and transact with autonomously.&lt;/p&gt;

&lt;p&gt;The MCP endpoint on houseofparfum.nl exposes these tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;— shopping —
ucp_list_products       ucp_get_product
create_checkout         get_checkout
update_checkout         complete_checkout
cancel_checkout         create_cart
get_cart                update_cart
cancel_cart             convert_cart
ucp_get_order           ucp_list_orders

— returns (KeepCard) —
keepcard_check_return_eligibility
keepcard_select_return_items
keepcard_submit_return_reason
keepcard_accept_keep_offer
keepcard_decline_keep_offer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;KeepCard.io&lt;/strong&gt; is a standalone returns intelligence platform. It connects to WooCommerce via REST API and Shopify via app installation. It owns the decision engine — return eligibility, keep-offer thresholds, fraud signals, monthly caps, discount code generation, and email delivery. None of that logic lives in the LLM.&lt;/p&gt;

&lt;p&gt;The UCPReady companion plugin for KeepCard exposes five MCP tools that let any agent drive the return flow conversationally, while KeepCard handles all the business logic and side effects server-side.&lt;/p&gt;




&lt;h2&gt;
  
  
  The important part: no AI in the business logic
&lt;/h2&gt;

&lt;p&gt;This is the part worth repeating.&lt;/p&gt;

&lt;p&gt;There is no hidden LLM orchestration inside the return engine. There is no model deciding business logic in the backend. There is no "if Claude says X, do Y" architecture.&lt;/p&gt;

&lt;p&gt;The business logic is deterministic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Return eligibility&lt;/li&gt;
&lt;li&gt;Order verification&lt;/li&gt;
&lt;li&gt;Keep-offer thresholds&lt;/li&gt;
&lt;li&gt;Duplicate protection&lt;/li&gt;
&lt;li&gt;Policy routing&lt;/li&gt;
&lt;li&gt;Store credit and discount issuance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The model is only the interface layer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That makes the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Model-agnostic — Claude, GPT-5, Gemini, Grok all work&lt;/li&gt;
&lt;li&gt;Easier to audit — no prompt-based rules buried in a system prompt&lt;/li&gt;
&lt;li&gt;Easier to harden — business rules are code, not inference&lt;/li&gt;
&lt;li&gt;More future-proof — swap the model, the commerce layer stays the same&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same session ran successfully with both Claude Sonnet 4.5 and GPT-5. Neither needed store-specific prompting. The protocol carries the context.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the return flow chains through UCPReady
&lt;/h2&gt;

&lt;p&gt;The integration works server-to-server. When the agent calls &lt;code&gt;keepcard_check_return_eligibility&lt;/code&gt;, UCPReady makes an HTTP POST to the KeepCard &lt;code&gt;/api/mcp/init&lt;/code&gt; endpoint with the store slug, order ID, and email. KeepCard resolves the order directly against WooCommerce or Shopify using its own stored credentials — UCPReady never touches the order data.&lt;/p&gt;

&lt;p&gt;The full call chain looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent → UCPReady MCP endpoint (WooCommerce)
      → KeepCard /api/mcp/* (cloud service)
      → WooCommerce or Shopify API (order verification)
      → KeepCard decision engine (keep-offer logic)
      → WooCommerce API (discount code creation)
      → KeepCard email stack (confirmation)
      → back through the chain to the agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM sees clean structured responses at each step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;keepcard_check_return_eligibility&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;response&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"eligible"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"needs_item_selection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I found order #85774. You have 30 day(s) left to return it. Which item(s) would you like to return?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"order"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"display_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"85774"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"days_remaining"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;keepcard_accept_keep_offer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;response&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"outcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kept"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"discount_code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"KEEP-3V9FNM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.00&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email_sent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Done! Your discount code is KEEP-3V9FNM — worth 1.00 EUR off your next order. A confirmation has been sent to your email."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;customer_message&lt;/code&gt; field means the agent surfaces KeepCard's language directly — it does not have to infer what to say from raw API data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this is different from what's already out there
&lt;/h2&gt;

&lt;p&gt;Most of what is marketed today as "AI returns" falls into one of these buckets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What it actually is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chatbot on a returns portal&lt;/td&gt;
&lt;td&gt;AI layer on top of an existing flow, still ends in a form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom automation project&lt;/td&gt;
&lt;td&gt;Tied to one model vendor, one store, one integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shopify-first AI returns&lt;/td&gt;
&lt;td&gt;Good tooling, but WooCommerce is a different ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refund infrastructure for devs&lt;/td&gt;
&lt;td&gt;Powerful but requires significant custom work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Those are real products. But they are still mostly AI applications sitting on top of commerce systems.&lt;/p&gt;

&lt;p&gt;What we are building is different: &lt;strong&gt;the commerce system itself becomes operable by agents.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;UCPReady exposes structured commerce capabilities through UCP and MCP. KeepCard.io exposes post-purchase capabilities through the same kind of machine-operable interface. The store does not wait for us to write Claude-specific code, GPT-specific prompts, or Gemini-specific workflows.&lt;/p&gt;

&lt;p&gt;The model is replaceable. The protocol is the product.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why WooCommerce specifically matters
&lt;/h2&gt;

&lt;p&gt;A lot of public examples in this space are Shopify-first. That makes sense — Shopify has a louder app ecosystem and more visible AI tooling.&lt;/p&gt;

&lt;p&gt;But what happened tonight was on WooCommerce.&lt;/p&gt;

&lt;p&gt;WooCommerce powers a large share of the world's online stores. It has almost no public examples of protocol-driven, frontier-model, post-purchase flows. The combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WooCommerce-native&lt;/li&gt;
&lt;li&gt;Protocol-native (UCP)&lt;/li&gt;
&lt;li&gt;Model-agnostic&lt;/li&gt;
&lt;li&gt;Agent-executable&lt;/li&gt;
&lt;li&gt;Post-purchase keep-offer logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;...is the category we are helping define. I have not seen a strong public proof of this combination before tonight.&lt;/p&gt;




&lt;h2&gt;
  
  
  What broke (because honesty matters)
&lt;/h2&gt;

&lt;p&gt;Tonight was not a perfect demo, and that is exactly why it was valuable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The keep-offer accept step was occasionally slow.&lt;/strong&gt; The final &lt;code&gt;keepcard_accept_keep_offer&lt;/code&gt; step sometimes exceeded the runner timeout budget. The business action still completed — coupon created, email sent, session marked kept — but the agent runner could report failure because the round trip took too long. This is a bridge latency problem, not a logic problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Duplicate finalization needed hardening.&lt;/strong&gt; When the first accept completed but the runtime retried, we needed the system to treat the retry as a successful terminal state rather than an error. Repeated accept should return: already processed, discount code, final state. That is what resilient agent commerce looks like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Mixed shopping and returns in one session exposed session contamination.&lt;/strong&gt; In one test, we ran search → purchase → return inside a single session. That exposed a session-orchestration issue: return tools were receiving stale search arguments from the earlier shopping flow. Not a KeepCard logic problem. A session-context boundary problem. The kind you only discover once the stack is real enough to chain these experiences together.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where we go next
&lt;/h2&gt;

&lt;p&gt;The hardest question has been answered: can a model search, buy, and then resolve a return with keep-offer logic on a live WooCommerce store through protocol-native rails?&lt;/p&gt;

&lt;p&gt;Yes. Tonight, it did.&lt;/p&gt;

&lt;p&gt;The remaining work is hardening, not architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reduce latency on the post-purchase bridge&lt;/li&gt;
&lt;li&gt;Keep terminal actions idempotent across retries&lt;/li&gt;
&lt;li&gt;Isolate session state between shopping and returns flows&lt;/li&gt;
&lt;li&gt;Test identity linking across multi-session journeys&lt;/li&gt;
&lt;li&gt;Validate the same lifecycle across more stores&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the remaining problems are latency budgets, idempotency, and session context boundaries — you are no longer asking whether the concept works. You are refining a working system.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bigger idea
&lt;/h2&gt;

&lt;p&gt;The goal is not "AI can help with returns."&lt;/p&gt;

&lt;p&gt;The goal is: &lt;strong&gt;stores should become operable by agents through standard protocols.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Search is one capability. Checkout is one capability. Payment is one capability. Returns are one capability. Keep-offers are one capability. Once those are exposed properly through open protocol rails, the model becomes interchangeable.&lt;/p&gt;

&lt;p&gt;That is when agentic commerce stops being a gimmick and starts becoming infrastructure.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;UCPReady: &lt;a href="https://zologic.nl/ucpready" rel="noopener noreferrer"&gt;zologic.nl/ucpready&lt;/a&gt; — WooCommerce UCP implementation&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;KeepCard.io: &lt;a href="https://keepcard.io" rel="noopener noreferrer"&gt;keepcard.io&lt;/a&gt; — Returns intelligence platform&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Session recording: &lt;a href="https://ucpplayground.com/s/01KQFVJJK6HZF2Z58MVMZQ3WXP" rel="noopener noreferrer"&gt;ucpplayground.com/s/01KQFVJJK6HZF2Z58MVMZQ3WXP&lt;/a&gt;&lt;/em&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Universal Commerce Protocol: &lt;a href="https://ucp.dev" rel="noopener noreferrer"&gt;ucp.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>woocommerce</category>
      <category>mcp</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
