Docs
Firmly Agentic Commerce
Set theme to dark (⇧+D)

UCP Implementation

This page covers the build details of Firmly’s UCP wrapper — service architecture, how UCP operations map to Firmly APIs, session state management, and the well-known manifest that Google uses for discovery.

​​ Service architecture

The UCP wrapper is a Firmly edge service that sits between Google and Firmly’s existing APIs. It translates UCP protocol requests into Firmly cart and checkout calls.

Component Purpose
Wrapper service Request routing, signature verification, API translation
Session store Session state mapping (UCP session ↔ Firmly cart), idempotency cache
Configuration Firmly API credentials, signing keys

Running at the edge gives low latency globally and DDoS protection, with the session store holding checkout session state.

​​ API mapping

The wrapper translates each UCP operation to one or more Firmly API calls. All use V2 except order completion, which uses V1:

UCP Operation Firmly API Call
Create session POST /api/v2/domains/{domain}/cart/line-items?flush_cart=false
Get session GET /api/v2/domains/{domain}/cart
Update shipping address POST /api/v2/domains/{domain}/cart/shipping-info
Update shipping method POST /api/v2/domains/{domain}/cart/shipment/methods
Apply / replace discount codes POST /api/v2/domains/{domain}/cart/promo-codes
Clear discount codes DELETE /api/v2/domains/{domain}/cart/promo-codes
Complete order (card / direct) POST /api/v1/domains/{domain}/cart/complete-order
Complete order (Google Pay) POST /api/v1/domains/{domain}/express/google-pay/complete-order
Cancel session No carts-service call — the session status is flipped to canceled in the session store

Key details:

  • flush_cart=false — line items are added without clearing existing cart contents
  • Shipping method — requires a shipment_id from the cart response to select a method
  • Discount codes — discounts.codes uses replacement semantics; the promo-codes endpoint is additive, so removals are handled as a clear-and-re-add
  • Cancel — cancellation only flips the checkout session’s status; it does not clear the backing cart
  • Device ID = Session ID — the UCP session ID is used as the Firmly device ID

​​ Session ID format

The UCP session ID is a UUID generated by Firmly’s UCP service. The same value identifies both the UCP checkout session and the underlying Firmly cart / device session, so the wrapper resolves the session against Firmly’s cart layer without any additional mapping step.


9cf76530-1344-420f-9ca6-c7a96fb6db45

UCP clients pass this value back in the path of subsequent calls (UpdateCheckoutSession, CompleteCheckoutSession) to refer to the same session.

​​ Session state

Each checkout session is held in the session store (the ucp_sessions KV namespace) under a session:{sessionId} key, and expires after 7 days:

Key pattern Value
session:{sessionId} { checkout_id, cart_id, domain, status, created_at, updated_at, expires_at, pending_shipping_info, submitted_shipping_info, selected_fulfillment_option_id }

The session stores both the Firmly cart mapping and transient data that UCP needs but Firmly doesn’t store natively — pending shipping info (for buffering partial address entry), the submitted shipping info, and the selected fulfillment option. The buyer’s email is folded into the pending shipping info; the payment instrument is supplied on the complete call and is not held in session state.

​​ Idempotency handling

Idempotency responses are stored separately from session metadata, in the same ucp_sessions KV namespace but under standalone keys of the form idempotency:{operation}:{key} — where operation is create, update, complete, or cancel (and the cart-resource equivalents cart_create, cart_update, cart_cancel). Each entry stores { response, fingerprint }, where the fingerprint is derived from the request body. When a request arrives with an Idempotency-Key header:

  1. Look up idempotency:{operation}:{key}
  2. If found and the fingerprint matches, return the cached response immediately
  3. If found but the fingerprint differs, reject with 409 (see Idempotency)
  4. If not found, process the request and store the response

Idempotency entries are honored for 24 hours (see Idempotency for the canonical behavior). This prevents duplicate orders from network retries or agent errors.

​​ Internal service communication

The UCP wrapper reaches Firmly’s cart and checkout services over Firmly’s internal network — traffic that never traverses the public internet, so no external integrator interacts with it. On each translated call the wrapper generates a device_id via crypto.randomUUID() (which doubles as the UCP session_id) and forwards it alongside the App ID.

​​ Well-known manifest

Google discovers Firmly’s UCP capabilities through a manifest served from the merchant’s own domain at /.well-known/ucp. The merchant forwards that path to Firmly, and api.firmly.work is the origin that generates the manifest (see UCP Configuration for how forwarding is set up):


GET https://<merchant-domain>/.well-known/ucp
Accept: application/json

The manifest tells Google what checkout capabilities are available, which endpoints to call, how to process payments, and how to verify response signatures. The example below is the current default (2026-04-08) manifest, redacted:


{
"ucp": {
"version": "2026-04-08",
"supported_versions": {
"2026-01-11": "https://staging.luma.gift/.well-known/ucp/2026-01-11",
"2026-01-23": "https://staging.luma.gift/.well-known/ucp/2026-01-23",
"2026-04-08": "https://staging.luma.gift/.well-known/ucp/2026-04-08"
},
"services": {
"dev.ucp.shopping": [
{
"version": "2026-04-08",
"spec": "https://ucp.dev/2026-04-08/specification/overview",
"transport": "rest",
"schema": "https://ucp.dev/2026-04-08/services/shopping/rest.openapi.json",
"endpoint": "https://api.firmly.work/api/2026-04-08/ucp/rest/domain/staging.luma.gift"
},
{
"version": "2026-04-08",
"spec": "https://ucp.dev/2026-04-08/specification/overview",
"transport": "mcp",
"schema": "https://ucp.dev/2026-04-08/services/shopping/mcp.openrpc.json",
"endpoint": "https://api.firmly.work/api/2026-04-08/ucp/mcp/domain/staging.luma.gift"
}
]
},
"capabilities": {
"dev.ucp.shopping.cart": [
{ "version": "2026-04-08", "spec": "https://ucp.dev/2026-04-08/specification/cart", "schema": "https://ucp.dev/2026-04-08/schemas/shopping/cart.json" }
],
"dev.ucp.shopping.checkout": [
{ "version": "2026-04-08", "spec": "https://ucp.dev/2026-04-08/specification/checkout", "schema": "https://ucp.dev/2026-04-08/schemas/shopping/checkout.json" }
],
"dev.ucp.shopping.fulfillment": [
{ "version": "2026-04-08", "spec": "https://ucp.dev/2026-04-08/specification/fulfillment", "schema": "https://ucp.dev/2026-04-08/schemas/shopping/fulfillment.json", "extends": "dev.ucp.shopping.checkout" }
],
"dev.ucp.shopping.discount": [
{ "version": "2026-04-08", "spec": "https://ucp.dev/2026-04-08/specification/discount", "schema": "https://ucp.dev/2026-04-08/schemas/shopping/discount.json", "extends": ["dev.ucp.shopping.cart", "dev.ucp.shopping.checkout"] }
],
"dev.ucp.shopping.catalog.search": [
{ "version": "2026-04-08", "spec": "https://ucp.dev/2026-04-08/specification/catalog/search", "schema": "https://ucp.dev/2026-04-08/schemas/shopping/catalog_search.json" }
],
"dev.ucp.shopping.catalog.lookup": [
{ "version": "2026-04-08", "spec": "https://ucp.dev/2026-04-08/specification/catalog/lookup", "schema": "https://ucp.dev/2026-04-08/schemas/shopping/catalog_lookup.json" }
]
},
"payment_handlers": {
"com.google.pay": [
{
"id": "gpay",
"version": "2026-01-23",
"spec": "https://pay.google.com/gp/p/ucp/2026-01-23/",
"config_schema": "https://pay.google.com/gp/p/ucp/2026-01-23/schemas/config.json",
"instrument_schemas": ["https://pay.google.com/gp/p/ucp/2026-01-23/schemas/card_payment_instrument.json"],
"config": { "gateway": "<psp-gateway>", "gatewayMerchantId": "<merchant-id>" }
}
]
}
},
"signing_keys": [
{
"kid": "ucp-firmly-key-1",
"kty": "EC",
"crv": "P-256",
"x": "<base64url_x>",
"y": "<base64url_y>",
"use": "sig",
"alg": "ES256"
}
]
}

The signing_keys array supports key rotation — add the new public key, start signing with it, then remove the old key after a transition period. signing_keys is a top-level sibling of ucp; payment_handlers is nested inside ucp. The Firmly Card handler (a proposal) also appears under payment_handlers (keyed by ai.firmly.card) for merchants where it is enabled.