Connect Service that exposes a commercetools Composable Commerce store to AI shopping agents — /llms.txt, /.well-known/ucp (UCP business profile), schema.org JSON-LD on PDPs (via API), and signed-JWT cart deeplinks that pre-fill a commercetools cart and redirect into the merchant's existing checkout.
Status: v0.2.0 — discovery surface, cart handoff, full ACP/UCP/AP2 endpoint stack on top of the @xpaysh/adapter-contract interface. Apache-2.0.
AI Agent (ChatGPT, Claude, Gemini, …)
│
│ 1. Fetches /llms.txt + /.well-known/ucp from the merchant's domain
│ 2. Negotiates capabilities, builds a cart, mints a signed deeplink
│
▼
merchant.example/?xpay_cart=<jwt>
│
│ 3. Storefront proxies /cart/deeplink to THIS SERVICE
▼
┌─────────────────────────────────────────┐
│ agentic-commerce-for-commercetools │
│ - Verifies the JWT (@xpaysh/cart- │
│ deeplinks; HS256 + sha256_hex(api_key)│
│ matches the WC plugin's verifier) │
│ - Creates a commercetools Cart via the │
│ CommercetoolsAdapter │
│ - 302s to the merchant's checkout │
│ with ?xpay_cart_id=<ct-cart-id> │
└──────────────────┬──────────────────────┘
│ commercetools Project API
▼
┌─────────────────────────────────────────┐
│ commercetools │
│ Products · Carts · Orders │
└──────────────────┬──────────────────────┘
│ merchant's existing payment integration
▼
Stripe / Adyen / Mollie / Braintree / etc.
The service is a standalone Node app running alongside the merchant's storefront (or as a commercetools Connect service). The storefront proxies a handful of paths to it.
| Method | Path | Notes |
|---|---|---|
| GET | /llms.txt |
llmstxt.org Markdown menu |
| GET | /.well-known/ucp |
UCP business profile (Google + Shopify + Etsy + Wayfair + Target + Walmart fetch this) |
| GET | /robots.txt |
AI-crawler allow blocks (template — storefront merges) |
| GET | /.well-known/agent-card.json |
A2A 1.0; opt-in via EMIT_AGENT_CARD=1 |
| GET | /.well-known/oauth-protected-resource |
RFC 9728; opt-in via EMIT_OAUTH_PROTECTED_RESOURCE=1 |
| GET | /api/v1/jsonld/product/:id[?slim=1] |
schema.org Product JSON-LD; storefront embeds in PDP HTML |
| GET | /cart/deeplink?token=<jwt> |
redeem cart-deeplink → CT cart → 302 to checkout |
| GET | /healthz |
liveness + commercetools reachability |
REST surface advertised in /.well-known/ucp under services.dev.ucp.shopping[0].endpoint. Snake-case JSON; minor-units money; ISO currency codes.
| Method | Path | Body / params |
|---|---|---|
| GET | /api/ucp/v1/catalog/search?q=&sku=&limit=&cursor=&sort= |
search params |
| GET | /api/ucp/v1/catalog/products/:id |
path |
| POST | /api/ucp/v1/carts |
{items[{sku,quantity,variant_id?}], currency?, external_id?} |
| GET | /api/ucp/v1/carts/:id |
— |
| PATCH | /api/ucp/v1/carts/:id |
{set_items[], remove_skus[], shipping_address, billing_address, discount_code} |
| POST | /api/ucp/v1/checkout |
{cart_id, shipping_address?, billing_address?, payment?} |
| GET | /api/ucp/v1/orders/:id |
— |
Request integrity: UCP requires RFC 9421 HTTP Message Signatures. v0.2 ships the endpoint behavior; full signature verification middleware is configurable (off by default; enable via VERIFY_UCP_SIGNATURES=1 once signing keys are populated in the profile — completes in v0.3).
Per-session surface — agent opens a checkout_session, mutates it, completes it.
| Method | Path | Body |
|---|---|---|
| POST | /api/acp/v1/checkout_sessions |
{items[{sku,qty,variation_id?}], currency?, capabilities_requested[], agent?, surface?, buyer_id?, external_id?} |
| GET | /api/acp/v1/checkout_sessions/:id |
— |
| POST | /api/acp/v1/checkout_sessions/:id |
{items[]?, remove_skus[]?, shipping_address?, billing_address?, discount_code?} |
| POST | /api/acp/v1/checkout_sessions/:id/complete |
{shipping_address?, billing_address?, payment?, note?} → Order |
| GET | /api/acp/v1/orders/:id |
— |
Session storage is in-memory in v0.2 — cold-start loses the session→cart mapping. (CT cart IS the source of truth; agents just need to re-open a session.) v0.3 moves session metadata to DynamoDB.
POST /api/acp/v1/delegate_payment — deferred to v0.3 (CP role).
| Method | Path | Body |
|---|---|---|
| POST | /api/ap2/v1/mandates/verify |
{mandate: "<jwt-vc>", require_audience?: boolean} |
| POST | /api/ap2/v1/checkout |
{cart_id, mandate, shipping_address?, billing_address?} |
Mandate signature verification is structural-only in v0.2 — the response includes signature_verified: false and signature_verification_status: "deferred_to_v0.3". Audience + expiry checks DO run. Full issuer-key fetching + VC signature verification lands in v0.3 alongside the CP role.
/llms.txt and /.well-known/ucp advertise xpay's hosted protocol endpoints (agent-commerce.xpay.sh/acp/v1/<slug>, …/ucp/v1/<slug>, …/ap2/v1/<slug>). Standalone-mode merchants can override to point at their own endpoints; xpay-commercial-tier merchants leave the defaults.
- ACP
POST /delegate_payment— agent supplies a delegated-payment credential; merchant captures via PSP. Requires the Credential Provider role. v0.3. - AP2 mandate signature verification — structural + audience + expiry checks pass; the actual issuer signature verification needs a trusted-issuer JWK fetcher. v0.3.
- UCP request-signature verification (RFC 9421) — middleware skeleton in place; defaults to off. Wire on once signing_keys are populated in the merchant's profile + agent platforms are signing. v0.3.
- Refunds + disputes —
adapter.refundOrder/adapter.openDispute.capabilities.{refunds, disputes} = false. v0.3. - Webhooks for order state changes — commercetools subscriptions.
capabilities.webhooks = false. v0.3.
The CommercetoolsAdapter v0.2 capabilities: {cart: true, checkout: true, catalogSearch: true, catalogLookup: true, order: true, refunds: false, disputes: false, inventoryRealtime: true, webhooks: false}.
- commercetools: in your Project → Settings → Developer Settings → API clients → "Create new API client". Use the "Mobile & single-page application client" template, or grant the minimum manual scopes for v0.1:
view_products,manage_my_orders. Copy the project key, client id, secret, scopes, auth URL, API URL. - xpay: get your merchant slug + api_key at
app.xpay.sh/onboard/commercetools(or, in standalone mode, mint your own slug + random api_key — anything works as long as the same key signs and verifies).
git clone https://github.com/xpaysh/agentic-commerce-for-commercetools.git
cd agentic-commerce-for-commercetools
cp .env.example .env
# Edit .env with your credentialsdocker compose -f examples/docker-compose.yml up --build
curl -sS http://localhost:8787/healthz | jq .npm install
npm run build
node dist/server.jsOr in dev mode (TypeScript directly, no build step):
npm run devAdd reverse-proxy rules to your storefront's web server (Vercel, Cloudflare, Nginx, Caddy, etc.):
/llms.txt → agentic.merchant.example:8787/llms.txt
/.well-known/ucp → agentic.merchant.example:8787/.well-known/ucp
/.well-known/oauth-protected-resource → agentic.merchant.example:8787/.well-known/oauth-protected-resource (if enabled)
/.well-known/agent-card.json → agentic.merchant.example:8787/.well-known/agent-card.json (if enabled)
/cart/deeplink → agentic.merchant.example:8787/cart/deeplink
For schema.org JSON-LD on PDPs, the storefront's product-page renderer calls:
const ld = await fetch(`https://agentic.merchant.example/api/v1/jsonld/product/${productId}`).then(r => r.json());
// then embed: <script type="application/ld+json">{ldJson}</script>For robots.txt, merge the AI-allow blocks from GET /robots.txt into your storefront's existing robots.txt (or proxy directly if you don't have one).
# Sign a deeplink using the same XPAY_API_KEY as the service
node -e "
const { signCartDeeplink } = require('@xpaysh/cart-deeplinks');
const { token } = signCartDeeplink({
merchant: process.env.XPAY_MERCHANT_SLUG,
items: [{ sku: 'SOME-SKU-IN-YOUR-CATALOG', qty: 1 }],
ttlSeconds: 300,
apiKey: process.env.XPAY_API_KEY,
});
console.log('https://merchant.example/cart/deeplink?token=' + token);
"
# Open the URL in a browser — you should land on /checkout?xpay_cart_id=<commercetools-cart-id>The same handler runs as a commercetools Connect service. Add a connect.yaml at the project root pointing the service entry point at dist/server.js. Connect provides the runtime + URL; you map your project's credentials via Connect's secrets manager. Manifest example coming in v0.2; for now, the Docker path is the supported one.
Pure-Node, no platform-specific bindings — runs anywhere. The Dockerfile ships a multi-stage build (Alpine + Node 20). For Lambda specifically, wrap buildHandler from src/server.ts in a Lambda-event adapter (the audit service's services/audit/src/lambda.js in xpaysh/agentic-commerce-plugin-template is the reference shape).
Adapt buildHandler to a fetch event handler. Worker adapter not included; contributions welcome.
| Var | Required | Notes |
|---|---|---|
XPAY_MERCHANT_SLUG |
✅ | Per-merchant identifier. Appears in xpay-hosted URLs. |
SITE_URL |
✅ | Public site URL the discovery files describe. Trailing slash recommended. |
SITE_NAME |
✅ | Display name (used in /llms.txt H1). |
XPAY_API_KEY |
✅ | Shared HS256 secret for cart-deeplink JWTs. |
CTP_PROJECT_KEY |
✅ | commercetools project key. |
CTP_CLIENT_ID |
✅ | OAuth client id (Project-scoped). |
CTP_CLIENT_SECRET |
✅ | OAuth client secret. |
CTP_SCOPES |
✅ | OAuth scopes, space-separated. |
CTP_AUTH_URL |
✅ | OAuth token endpoint URL (region-dependent). |
CTP_API_URL |
✅ | Project API endpoint URL (region-dependent). |
SITE_DESCRIPTION |
One-line description for /llms.txt. |
|
CHECKOUT_PATH |
Path the cart-deeplink redirects to. Default /checkout. |
|
HOST |
Bind host. Default 0.0.0.0. |
|
PORT |
Bind port. Default 8787. |
|
EMIT_OAUTH_PROTECTED_RESOURCE |
1 to emit /.well-known/oauth-protected-resource (enable with UCP OAuth Identity Linking). |
|
EMIT_AGENT_CARD |
1 to emit /.well-known/agent-card.json (A2A 1.0; watchlist). |
|
CTP_TOKEN_TTL_SECONDS |
commercetools bearer-token cache TTL. Default 1800. |
import { CommercetoolsAdapter, CommercetoolsClient, loadConfig } from 'agentic-commerce-for-commercetools';
const config = loadConfig();
const ct = new CommercetoolsClient(config.ct);
const adapter = new CommercetoolsAdapter({ ct, siteUrl: config.siteUrl });
const product = await adapter.getProduct('some-product-id');
const cart = await adapter.createCart({ items: [{ sku: 'SKU-1', quantity: 1 }] });The adapter implements @xpaysh/adapter-contract's PlatformAdapter, so any code written against that interface (the hosted backend, the audit's commerce checks, sibling-plugin orchestration) accepts this adapter unchanged.
| npm package | Role |
|---|---|
@xpaysh/adapter-contract |
TypeScript PlatformAdapter interface |
@xpaysh/discovery |
/llms.txt, schema.org JSON-LD, robots.txt, A2A agent-card, RFC 9728 generators |
@xpaysh/ucp-schemas |
UCP profile generator (all 6 core + 2 extension capabilities); sh.xpay namespace |
@xpaysh/cart-deeplinks |
HS256 cart-handoff JWT (wire-compatible with the WC plugin) |
After deploying:
# Anonymous audit
npx @xpaysh/storefront-audit https://store.merchant.example/
# Or browse to:
# https://audit.xpay.sh/?url=https://store.merchant.example/- Plugin template + family monorepo
- Reference plugin (WooCommerce, PHP) — v0.2+ ships the same wire format
- docs.xpay.sh — ACP vs UCP vs AP2
- Agentic commerce roadmap
- commercetools Composable Commerce
Apache-2.0.