When Google announced the Universal Commerce Protocol in January 2026, I was immediately curious. An open standard that lets AI agents buy things. Is the internet actually dead now? I remember talking with Reid months ago about a future that looks like this. My initial thinking is that it is like buying via Alexa but seemingly better. I then figured this may the first marker that signals the end of Google Search unless gemini takes some additional steps.

Ultimately, I decided to find out by building it.

This is the story of integrating UCP into a BigCommerce store for one of our ecommerce clients—a clothing and accessories company running on BigCommerce. Here’s what I learned about agentic commerce, the actual implementation challenges, and why payment escalation became the unexpected hero of this project. I figured this would push them to the forefront of agentic commerce. I can’t imagine there being that may small businesses that are currently playing in this space. I’m pretty excited for what’s to come here!

What UCP Actually Is

Before diving into code, let’s get clear on what we’re building for.

UCP is an open-source standard developed by Google with 20+ partners including Shopify, Etsy, Walmart, and Target. The goal: solve the N×N integration problem where every AI agent needs custom integrations with every merchant.

Instead of that mess, UCP defines a universal protocol. Merchants expose capabilities through a JSON manifest at /.well-known/ucp. Agents discover what’s available, then use standard REST endpoints to create checkout sessions, add shipping info, select fulfillment options, and complete purchases.

Three parts to this:

  • Discovery: Agents find merchants via the UCP manifest
  • Sessions: Stateful checkout flows with idempotency guarantees
  • Payment handlers vs. instruments: The protocol decouples what consumers use (Google Pay, Apple Pay) from how merchants process payments

That last point matters. UCP doesn’t force merchants onto a specific payment processor. You keep your existing setup—the protocol just standardizes how agents interact with it.

The Integration Architecture

Here’s the full stack for the integration:

Customer → AI Agent (Gemini, Claude, etc.)

         UCP Worker (Cloudflare)

         BigCommerce APIs

         ShipperHQ (shipping rates)

         GMC Catalog (product discovery)

Three main components:

  1. GMC Catalog Sync — Python CLI that transforms BigCommerce products into Google Merchant Center XML format
  2. UCP Worker — Cloudflare Worker handling the UCP protocol, session management, and BigCommerce bridging
  3. AI Discoverability Layer — llms.txt, FAQPage schema, enhanced Product schema, and robots.txt directives

Let me walk through each.

GMC Catalog Sync: The Discovery Layer

AI agents need to discover products before they can sell them. Google Merchant Center is the canonical source—agents query GMC to find products matching user intent.

The challenge: BigCommerce stores products in one format. GMC expects another. And for apparel, GMC has strict requirements around variants, GTINs, and product attributes.

I built gmc-catalog-sync as a Poetry-managed Python CLI. Core transformation logic:

def _gmc_id(product_id: int, variant_id: int) -> str:
    """Build GMC id: '{productId}.{variantId}'."""
    return f"{product_id}.{variant_id}"

def transform_product(
    product: BCProduct,
    categories: dict[int, str],
) -> list[GMCProduct]:
    """Transform a BC product into one or more GMC catalog items.

    Multi-variant products: one item per non-disabled variant.
    Single/no-variant: one item using product-level data.
    """

The ID format is critical. I use {productId}.{variantId} (e.g., "3343.8901") because agents need both identifiers to add items to cart. The UCP Worker parses this composite ID when creating checkout sessions.

GTIN Validation

Google requires GTINs (barcodes) for most branded products. Problem: not all products have valid GTINs in BigCommerce.

def _get_identifier(
    product: BCProduct, variant_sku: str, variant_upc: str, variant_mpn: str
) -> tuple[str, str, bool]:
    """Determine GTIN, MPN, and identifier_exists for a product/variant."""
    gtin = variant_upc or product.upc
    mpn = variant_mpn or variant_sku or product.mpn or product.sku

    # Clean GTIN - must be numeric and 8-14 digits
    if gtin:
        gtin_clean = "".join(c for c in gtin if c.isdigit())
        if len(gtin_clean) >= 8 and len(gtin_clean) <= 14:
            gtin = gtin_clean
        else:
            gtin = ""  # Invalid GTIN format

    # identifier_exists is False only if we have neither GTIN nor MPN
    identifier_exists = bool(gtin or mpn)

    return gtin, mpn, identifier_exists

The identifier_exists field is the escape hatch. When a product genuinely doesn’t have standard identifiers (handmade goods, custom items), you set this to false and GMC accepts it. Use it sparingly though—products without identifiers get deprioritized in search results.

Apparel Attributes

GMC requires additional attributes for apparel: color, size, gender, age group, material. I extract these from BigCommerce variant options and category assignments:

# Extract variant attributes
v_color, v_size = _extract_variant_attrs(variant.option_values)

# Apparel attributes derived from categories
gender = get_gender(all_cat_names)
age_group = get_age_group(all_cat_names)
material = "Cotton"  # Brand default

The output is XML following Google’s RSS 2.0 spec with g: namespace extensions:

<item>
  <g:id>3343.8901</g:id>
  <g:title>Cotton Crew Socks</g:title>
  <g:item_group_id>3343</g:item_group_id>
  <g:color>Natural</g:color>
  <g:size>Large</g:size>
  <g:gender>unisex</g:gender>
  <g:material>Cotton</g:material>
  <g:identifier_exists>true</g:identifier_exists>
</item>

The XML gets uploaded to Google Cloud Storage. GMC fetches it on a schedule—usually daily.

UCP Worker: The Protocol Implementation

This is the core of the integration. A Cloudflare Worker (TypeScript) implementing the full UCP spec at version 2026-01-23.

The Manifest

Every UCP integration starts with discovery. Agents fetch /.well-known/ucp to learn what a merchant supports:

export const UCP_PROFILE: UCPProfile = {
  ucp: {
    version: '2026-01-23',
    services: {
      'dev.ucp.shopping': {
        version: '2026-01-23',
        spec: 'https://ucp.dev/specification/checkout/',
        rest: {
          schema: 'https://ucp.dev/specification/checkout-rest/',
          endpoint: 'https://example-store.com',
        },
      },
    },
    capabilities: [
      {
        name: 'dev.ucp.shopping.checkout',
        version: '2026-01-23',
        spec: 'https://ucp.dev/specification/checkout/',
      },
      {
        name: 'dev.ucp.shopping.fulfillment',
        version: '2026-01-23',
        extends: 'dev.ucp.shopping.checkout',
      },
    ],
  },
  payment: {
    handlers: [
      {
        id: 'merchant-google-pay',
        name: 'dev.ucp.payments.google-pay',
        version: '2026-01-23',
        config: { type: 'PAYMENT_GATEWAY' },
      },
    ],
  },
};

This tells agents: “I support checkout and fulfillment capabilities at the latest spec version, and I can handle Google Pay.”

Session Lifecycle

UCP uses checkout sessions to manage state. The lifecycle:

  1. Create (POST /checkout-sessions) — Agent sends line items, gets back a session ID
  2. Update (PUT /checkout-sessions/{id}) — Add buyer info, shipping address, select shipping option
  3. Complete (POST /checkout-sessions/{id}/complete) — Finalize the purchase
  4. Cancel (POST /checkout-sessions/{id}/cancel) — Abort

The session state machine:

type UCPSessionStatus =
  | 'incomplete'        // Missing required info
  | 'requires_escalation'  // Payment needs browser handoff
  | 'ready_for_complete'   // All info present, ready to finalize
  | 'completed'         // Order placed
  | 'canceled';         // Session abandoned

The worker bridges UCP sessions to BigCommerce carts and checkouts:

export async function createSession(
  env: Env,
  request: UCPSessionCreateRequest
): Promise<{ response: UCPSessionResponse; status: number }> {
  const bc = new BigCommerceClient(env);
  const sessionId = generateSessionId();

  const bcLineItems = request.line_items.map((item) => {
    const { productId, variantId } = parseProductId(item.item_id);
    return { product_id: productId, variant_id: variantId, quantity: item.quantity };
  });

  const cart = await bc.createCart(bcLineItems, request.buyer?.email);

  // Store state in Cloudflare KV
  const state: UCPSessionState = {
    id: sessionId,
    status: 'incomplete',
    bc_cart_id: cart.id,
    currency: cart.currency?.code || 'USD',
    // ...
  };

  await env.UCP_SESSIONS.put(sessionId, JSON.stringify(state), {
    expirationTtl: SESSION_TTL,
  });

  return { response, status: 201 };
}

Idempotency: The Part You Can’t Skip

Network requests fail. Agents retry. Without idempotency, you create duplicate carts and orders.

UCP requires an Idempotency-Key header on state-modifying requests. My implementation uses SHA-256 to hash the request body and detect whether a retry has the same payload:

export async function hashBody(body: string): Promise<string> {
  const encoder = new TextEncoder();
  const data = encoder.encode(body);
  const hashBuffer = await crypto.subtle.digest('SHA-256', data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
}

export async function checkIdempotency(
  env: Env,
  key: string,
  bodyHash: string
): Promise<{ cached: true; response: Response } | { cached: false } | { conflict: true }> {
  const existing = await env.UCP_SESSIONS.get(`idem:${key}`);

  if (!existing) return { cached: false };

  const entry = JSON.parse(existing);
  if (entry.bodyHash !== bodyHash) {
    return { conflict: true };  // Same key, different body = conflict
  }

  return { cached: true, response: /* cached response */ };
}

Three outcomes:

  • No entry: First request, process normally
  • Same body hash: Replay cached response
  • Different body hash: 409 Conflict—something’s wrong

Stripe does the same thing. The body hash is what makes it work—without it, you can’t tell legitimate retries from misuse.

Payment Escalation: The BigCommerce Reality

UCP envisions tokenized payments where agents complete purchases without browser handoffs. Google Pay tokens, stored payment methods, the whole deal.

BigCommerce doesn’t support that via API. You can create carts, add consignments, calculate shipping—but when it’s time to charge a card, you redirect to their hosted checkout.

So I implemented payment escalation:

export async function completeSession(
  env: Env,
  sessionId: string
): Promise<{ response: UCPSessionResponse; status: number }> {
  // ... validate session is ready_for_complete ...

  // BigCommerce requires payment escalation — we can't complete via API
  state.status = 'requires_escalation';
  state.continue_url = `${env.BC_STORE_URL}/cart.php?action=loadInCheckout&id=${state.bc_cart_id}`;

  const escalationMessage: UCPMessage = {
    type: 'info',
    code: 'PAYMENT_ESCALATION',
    content: 'Payment must be completed in browser',
    severity: 'medium',
  };

  // ... return response with continue_url ...
}

The status: 'requires_escalation' tells the agent: “I can’t finish this programmatically. Send the user to continue_url to complete payment.”

Not as clean as fully automated checkout. But it works. Customers still get agent-assisted product discovery and cart setup. They just finish payment in a familiar checkout flow.

AI Discoverability Enhancements

UCP handles the purchase flow. Agents also need to answer questions about a brand, find products, understand policies.

llms.txt

I added an llms.txt file at the site root—plain-text summary for LLM consumption:

# [Brand Name]

> Clothing and accessories.

## Products
- Socks (crew, ankle, knee-high)
- Apparel (t-shirts, leggings, underwear)
- Accessories (scarves, hats)

## Policies
- Free shipping on orders over $75
- 30-day returns on unworn items
- Made in USA

## Contact
- support@example-store.com
- 1-800-xxx-xxxx

Served via Cloudflare Worker with caching headers. Think of it as README.md for AI agents.

FAQPage Schema

I extracted 20 Q&A pairs from the client’s Gorgias Help Center and injected them as FAQPage structured data:

{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "What materials do you use?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "We use certified cotton..."
      }
    }
  ]
}

Google surfaces these in search results. For agentic commerce, agents can parse this schema to answer customer questions without hallucinating.

Enhanced Product Schema

The default Product schema in BigCommerce templates is minimal. I added apparel-specific properties:

{
  "@type": "Product",
  "material": "Cotton",
  "additionalProperty": [
    {
      "@type": "PropertyValue",
      "name": "Certification",
      "value": "Fair Trade Certified"
    }
  ],
  "offers": {
    "@type": "AggregateOffer",
    "hasMerchantReturnPolicy": { /* ... */ }
  }
}

The material property and certifications help agents understand product attributes. hasMerchantReturnPolicy gives return policy info inline.

robots.txt AI Bot Directives

I updated robots.txt to explicitly allow AI crawlers:

User-agent: GPTBot
Allow: /

User-agent: ClaudeBot
Allow: /

User-agent: PerplexityBot
Allow: /

User-agent: Google-Extended
Allow: /

Opt-in. Some sites block these bots. For agentic commerce to work, you need to let agents crawl your content.

Lessons Learned

Payment limitations aren’t blockers

I worried BigCommerce’s API limitations would sink the project. Payment escalation is fine. Users are comfortable completing checkout in a browser—they value the agent doing the research and cart setup.

GTIN compliance takes time

Small brands often don’t have proper GTINs for all products. The identifier_exists: false fallback works, but products without identifiers get deprioritized in GMC. Budget time for cleaning up product data.

Idempotency is non-negotiable

Distributed systems fail in weird ways. Without proper idempotency handling, you create duplicate carts, double-charge customers, or worse. Build it right from the start.

Session state management matters

Cloudflare KV for session storage with 24-hour TTL. Simple and cheap. Higher volume would need something more robust—but KV handles this client’s scale easily.

Testing with actual AI agents is humbling

I tested with Claude and Gemini. Both found edge cases I hadn’t considered—malformed requests, unusual product combinations, retry patterns. The spec is one thing; real-world agent behavior is another.

What’s Next

The integration is deployed and working. Next steps:

  1. Webhook integration — Listen for BigCommerce order webhooks to transition sessions from requires_escalation to completed automatically
  2. Expanded capabilities — Discounts, gift cards, subscription products
  3. Multi-merchant orchestration — The real power of UCP shows when agents coordinate across multiple merchants

Try It Yourself

Two independent projects:

  • gmc-catalog-sync/ — Python CLI (Poetry project)
  • ucp-worker/ — Cloudflare Worker (TypeScript)

Both designed to be adapted. The GMC sync tool works with any BigCommerce store. The UCP Worker can bridge to other platforms with modified client code.

UCP gives us a standard. Now we build the infrastructure.


Built for an ecommerce client on BigCommerce. UCP spec v2026-01-23.