<?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 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>
    <item>
      <title>60 seconds to see if your webstore supports the latests UCP protocol. Give it a run!</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Thu, 30 Apr 2026 12:03:40 +0000</pubDate>
      <link>https://dev.to/zologic/60-seconds-to-see-if-your-webstore-supports-the-latests-ucp-protocol-give-it-a-run-364j</link>
      <guid>https://dev.to/zologic/60-seconds-to-see-if-your-webstore-supports-the-latests-ucp-protocol-give-it-a-run-364j</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/benjifisher/is-my-store-ucp-ready-how-to-check-in-60-seconds-4fco" class="crayons-story__hidden-navigation-link"&gt;Is My Store UCP Ready? How to Check in 60 Seconds&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/benjifisher" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3787687%2F0c8176d8-b238-43f2-b0af-71689e955123.jpg" alt="benjifisher profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/benjifisher" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Benji Fisher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Benji Fisher
                
              
              &lt;div id="story-author-preview-content-3591133" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/benjifisher" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3787687%2F0c8176d8-b238-43f2-b0af-71689e955123.jpg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Benji Fisher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/benjifisher/is-my-store-ucp-ready-how-to-check-in-60-seconds-4fco" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 30&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/benjifisher/is-my-store-ucp-ready-how-to-check-in-60-seconds-4fco" id="article-link-3591133"&gt;
          Is My Store UCP Ready? How to Check in 60 Seconds
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ecommerce"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ecommerce&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ucp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ucp&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/benjifisher/is-my-store-ucp-ready-how-to-check-in-60-seconds-4fco" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/benjifisher/is-my-store-ucp-ready-how-to-check-in-60-seconds-4fco#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              1&lt;span class="hidden s:inline"&gt;&amp;nbsp;comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>The 0.2% flawless rate from your April report is wild. Most stores have a manifest. Almost none actually work end-to-end. It's a conformance problem, not an infrastructure problem — and that's exactly what this score makes visible.
Love what you built here</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Wed, 29 Apr 2026 11:29:46 +0000</pubDate>
      <link>https://dev.to/zologic/the-02-flawless-rate-from-your-april-report-is-wild-most-stores-have-a-manifest-almost-none-4ngi</link>
      <guid>https://dev.to/zologic/the-02-flawless-rate-from-your-april-report-is-wild-most-stores-have-a-manifest-almost-none-4ngi</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/benjifisher/introducing-the-ucp-score-a-0-100-agent-readiness-grade-for-every-ucp-store-1851" class="crayons-story__hidden-navigation-link"&gt;Introducing the UCP Score: A 0–100 Agent-Readiness Grade for Every UCP Store&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/benjifisher" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3787687%2F0c8176d8-b238-43f2-b0af-71689e955123.jpg" alt="benjifisher profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/benjifisher" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Benji Fisher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Benji Fisher
                
              
              &lt;div id="story-author-preview-content-3585672" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/benjifisher" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3787687%2F0c8176d8-b238-43f2-b0af-71689e955123.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Benji Fisher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/benjifisher/introducing-the-ucp-score-a-0-100-agent-readiness-grade-for-every-ucp-store-1851" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 29&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/benjifisher/introducing-the-ucp-score-a-0-100-agent-readiness-grade-for-every-ucp-store-1851" id="article-link-3585672"&gt;
          Introducing the UCP Score: A 0–100 Agent-Readiness Grade for Every UCP Store
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ecommerce"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ecommerce&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/product"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;product&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ucp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ucp&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/benjifisher/introducing-the-ucp-score-a-0-100-agent-readiness-grade-for-every-ucp-store-1851" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;5&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/benjifisher/introducing-the-ucp-score-a-0-100-agent-readiness-grade-for-every-ucp-store-1851#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              2&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>AP2 Mandates Are Live on UCPReady — Here's What That Actually Means for Autonomous Payment</title>
      <dc:creator>Almin Zolotic</dc:creator>
      <pubDate>Fri, 24 Apr 2026 19:42:29 +0000</pubDate>
      <link>https://dev.to/zologic/ap2-mandates-are-live-on-ucpready-heres-what-that-actually-means-for-autonomous-payment-1bba</link>
      <guid>https://dev.to/zologic/ap2-mandates-are-live-on-ucpready-heres-what-that-actually-means-for-autonomous-payment-1bba</guid>
      <description>&lt;p&gt;Zero merchants in &lt;a href="https://dev.to/benjifisher/agentic-commerce-optimization-what-4491-merchants-reveal-about-ucp-readiness-3fk"&gt;Ben Fisher's 4,024-merchant UCP dataset&lt;/a&gt; support native payment. I've been building toward closing that gap since March. Today it's done — at least on the business side.&lt;/p&gt;

&lt;p&gt;This post is about AP2 Mandates: what they are, why they're the only spec-compliant path to autonomous card payment on UCP, and what it took to implement them correctly on WooCommerce.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why every UCP checkout still ends with a browser redirect
&lt;/h2&gt;

&lt;p&gt;When an AI agent completes a UCP checkout today, it gets a &lt;code&gt;continue_url&lt;/code&gt;. The buyer clicks a link, lands on the WooCommerce checkout page, fills in their card, and pays. The agent handled discovery and session setup. The human handled payment.&lt;/p&gt;

&lt;p&gt;That handoff exists because the spec requires it — unless one specific extension is active.&lt;/p&gt;

&lt;p&gt;From the UCP checkout specification:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The checkout has to be finalized manually by the user through a trusted UI &lt;strong&gt;unless the AP2 Mandates extension is supported&lt;/strong&gt;."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That "unless" is the entire autonomous payment story in UCP. Not Stripe tokens. Not saved cards. Not wallet APIs. &lt;strong&gt;AP2 Mandates.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What AP2 Mandates actually are
&lt;/h2&gt;

&lt;p&gt;AP2 is a cryptographic authorization framework. When it's negotiated between a business and a platform, the checkout session is "security locked" — neither party can revert to an unprotected flow.&lt;/p&gt;

&lt;p&gt;The flow has two sides:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Business side:&lt;/strong&gt; Every checkout response must include &lt;code&gt;ap2.merchant_authorization&lt;/code&gt; — a JWS detached signature proving the checkout terms (price, line items, totals) are authentic and haven't been tampered with. The signature is ES256, JCS-canonicalized per RFC 8785, with the payload excluded from the JWS body (RFC 7515 Appendix F detached content format).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Platform side:&lt;/strong&gt; When the user confirms the purchase, the platform generates a cryptographically signed mandate — an SD-JWT credential proving the user explicitly authorized this specific transaction. It submits that mandate at &lt;code&gt;complete_checkout&lt;/code&gt;. The business verifies it. If valid, payment proceeds without a browser redirect.&lt;/p&gt;

&lt;p&gt;The checkout mandate contains the full checkout response including the business's &lt;code&gt;merchant_authorization&lt;/code&gt;. So the platform's signature covers the business's signature. It's a nested cryptographic binding: the business proves the terms, the platform proves the user consented to those exact terms.&lt;/p&gt;




&lt;h2&gt;
  
  
  What implementing this actually required
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Getting the signing right
&lt;/h3&gt;

&lt;p&gt;The spec says ES256 with detached JWS. In PHP, &lt;code&gt;openssl_sign()&lt;/code&gt; on a P-256 key produces a DER-encoded ECDSA signature. JWS ES256 requires raw &lt;code&gt;r||s&lt;/code&gt; — 64 bytes for P-256. Those are not the same format.&lt;/p&gt;

&lt;p&gt;Every JWS library you'd use on the platform side (jose, python-jose, jsonwebtoken) expects raw r||s. A DER signature will fail verification silently or throw a malformed signature error. The fix is a DER-to-raw-rs converter:&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="c1"&gt;// openssl_sign() → DER. JWS ES256 → raw r||s. Not the same.&lt;/span&gt;
&lt;span class="nb"&gt;openssl_sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$signing_input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$signature_der&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$private_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;OPENSSL_ALGO_SHA256&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$der_inner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$r_offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$r_len&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$der_inner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$s_offset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$s_len&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$signature_raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str_pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;ltrim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;STR_PAD_LEFT&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;str_pad&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;ltrim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x00&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;STR_PAD_LEFT&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JCS canonicalization (RFC 8785) is the other non-obvious requirement. Before signing, the checkout payload is canonicalized — keys sorted recursively, Unicode normalized, numbers in IEEE 754 format. This ensures the signature is reproducible across systems that may re-serialize JSON differently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enforcing the mandate at complete_checkout
&lt;/h3&gt;

&lt;p&gt;The spec is explicit: if AP2 was negotiated, &lt;code&gt;complete_checkout&lt;/code&gt; MUST reject requests without &lt;code&gt;ap2.checkout_mandate&lt;/code&gt;. This is the security boundary. Without it, AP2 is advertised but provides no protection — a platform could skip the mandate entirely and the checkout would succeed.&lt;/p&gt;

&lt;p&gt;The enforcement block runs before order creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$ap2_ext&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;is_ap2_configured&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="nv"&gt;$mandate_jwt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'ap2'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s1"&gt;'checkout_mandate'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$mandate_jwt&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="c1"&gt;// Return mandate_required error — session stays ready_for_complete&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;mandate_error_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'mandate_required'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$session&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nv"&gt;$mandate_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$ap2_ext&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;verify_checkout_mandate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$mandate_jwt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nv"&gt;$mandate_error&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;mandate_error_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$mandate_error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'code'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$session&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verifying the mandate
&lt;/h3&gt;

&lt;p&gt;When a mandate is present, the full verification chain runs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parse the SD-JWT structure (&lt;code&gt;header.payload.signature~disclosures~keybinding&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Check expiry (&lt;code&gt;exp&lt;/code&gt; claim)&lt;/li&gt;
&lt;li&gt;Extract the embedded checkout from mandate claims&lt;/li&gt;
&lt;li&gt;Re-verify &lt;code&gt;merchant_authorization&lt;/code&gt; — confirm the platform wrapped our own signature, not a different checkout&lt;/li&gt;
&lt;li&gt;Verify checkout ID matches the current session&lt;/li&gt;
&lt;li&gt;Verify totals match — no bait-and-switch between what the user saw and what gets charged&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Platform key verification
&lt;/h3&gt;

&lt;p&gt;The final step: verifying the SD-JWT outer signature using the platform's public key. This is what proves the mandate is genuinely from the platform and not forged.&lt;/p&gt;

&lt;p&gt;The flow: decode the JWS header → extract &lt;code&gt;alg&lt;/code&gt; and &lt;code&gt;kid&lt;/code&gt; → fetch the platform's UCP profile from the &lt;code&gt;UCP-Agent&lt;/code&gt; header → pull &lt;code&gt;signing_keys&lt;/code&gt; → find the JWK matching &lt;code&gt;kid&lt;/code&gt; → build an OpenSSL public key from the JWK &lt;code&gt;x&lt;/code&gt;/&lt;code&gt;y&lt;/code&gt; coordinates → verify.&lt;/p&gt;

&lt;p&gt;Building an EC public key from JWK coordinates without a library means constructing the SubjectPublicKeyInfo DER by hand in PHP — OID encoding, SEQUENCE wrapping, BIT STRING for the uncompressed EC point. It's ~80 lines but removes a runtime dependency and works on any WordPress host.&lt;/p&gt;

&lt;p&gt;The platform profile is fetched over HTTPS and cached with WP transients, respecting &lt;code&gt;Cache-Control max-age&lt;/code&gt; with a 60-second floor. Verification result is logged with kid, algorithm, and session ID for auditability.&lt;/p&gt;




&lt;h2&gt;
  
  
  The capability name that broke everything
&lt;/h2&gt;

&lt;p&gt;Before any of this verification mattered, there was a simpler bug: the capability was advertised as &lt;code&gt;dev.ucp.shopping.ap2_mandates&lt;/code&gt; — plural. The spec uses &lt;code&gt;dev.ucp.shopping.ap2_mandate&lt;/code&gt; — singular.&lt;/p&gt;

&lt;p&gt;Capability negotiation is a string equality check. The intersection algorithm finds no match between &lt;code&gt;ap2_mandates&lt;/code&gt; and &lt;code&gt;ap2_mandate&lt;/code&gt;. AP2 is never activated. Every checkout session falls back to delegate payment. No error, no warning — it silently never negotiates.&lt;/p&gt;

&lt;p&gt;One character. Every implementation should validate capability names against the spec registry rather than trusting their own strings.&lt;/p&gt;




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

&lt;p&gt;When &lt;code&gt;dev.ucp.shopping.ap2_mandate&lt;/code&gt; is enabled in UCPReady and a compatible platform connects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every checkout response includes &lt;code&gt;ap2.merchant_authorization&lt;/code&gt; (JWS ES256, detached, JCS payload)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;complete_checkout&lt;/code&gt; rejects requests without &lt;code&gt;ap2.checkout_mandate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Verification runs: SD-JWT parse → expiry → embedded checkout extraction → merchant_authorization re-verification → ID and totals match → platform key verification&lt;/li&gt;
&lt;li&gt;If all checks pass, payment proceeds — no browser redirect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The one remaining piece is ecosystem readiness: a platform that supports AP2 mandate submission and forwards the access token that proves identity linking. &lt;a href="https://ucpplayground.com" rel="noopener noreferrer"&gt;Ben Fisher's UCPPlayground&lt;/a&gt; is the logical first test. Once that's connected, UCPReady will produce the first confirmed AP2-mandate-verified autonomous purchase on WooCommerce.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters beyond WooCommerce
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/benjifisher/the-state-of-agentic-commerce-april-2026-l93"&gt;Ben's dataset of 4,024 merchants&lt;/a&gt; shows zero native payment support. Part of that is the SPT/ACS story — Stripe's Shared Payment Token is US-only right now and requires Stripe to host the checkout flow. That's a different architecture than UCP.&lt;/p&gt;

&lt;p&gt;On UCP, AP2 Mandates is the spec's answer. It's protocol-agnostic — the mandate can cover any payment method handled by any PSP. The platform proves user consent cryptographically. The business verifies that proof and charges via their existing payment gateway. No card data crosses protocol boundaries. PSD2 SCA compliance comes from the mandate being platform-issued and buyer-authenticated at mandate creation time.&lt;/p&gt;

&lt;p&gt;This is what autonomous agent commerce looks like when the merchant controls the checkout instead of delegating it to Stripe's infrastructure.&lt;/p&gt;




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

&lt;p&gt;If you're building a platform that supports UCP and want to test AP2 mandate submission against a live endpoint, reach out. houseofparfum.nl is running UCPReady 1.8.23 with AP2 ready to activate.&lt;/p&gt;

</description>
      <category>woocommerce</category>
      <category>ucp</category>
      <category>agenticcommerce</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
