UCP Checker
Guides · Retail

How UCP’s Embedded Transport Works: The Store’s Checkout, Step by Step

· 30 October 2026 · 12 min read

UCP, the open Universal Commerce Protocol, has two ways to hand a checkout from an agent back to the store. The plain one is a link: the buyer opens the checkout's continue_url and finishes on the store's site. The other is the Embedded transport: the agent's app opens the store's own checkout page inside itself, and the two exchange messages while the buyer finishes.

We built both sides: a test florist in UCP Sandbox whose checkout can be embedded, and hosting in UCP Playground. This walkthrough follows one real order through it, step by step, from the agent's checkout to six messages and a placed order. It is the companion to our walkthrough of UCP over A2A.

What is UCP's Embedded transport?

UCP's Embedded transport lets an app (the spec calls it the host) load a store's checkout page in an iframe or webview and exchange JSON-RPC messages with it. For checkout, the spec calls this the Embedded Checkout Protocol (ECP): the store's page tells the host when it is ready, when the checkout changes and when the order is placed, and the host can take over chosen steps, such as supplying the payment credential.

The store keeps its own page, rules and order. Embedded doesn't replace REST or MCP: the agent still builds the checkout over one of those, and Embedded is how the buyer finishes it.

The setup

  • The store: Northlight Flowers on flowers-embedded.ucpsandbox.com, a test florist in UCP Sandbox whose profile offers REST, MCP and Embedded. Nothing is charged and nothing ships.
  • The host and the agent: UCP Playground, running Claude Sonnet 5, with checkout set to "Store's checkout · Embedded".
  • The task: buy one Bouquet of Red Roses, with free delivery to Sam Lee, 123 Market St, San Francisco.

The run is public, recorded on 30 October 2026: four agent calls, six messages from the store's page, two answers from Playground.

UCP Playground set up for the run: the UCP pill reads "UCP · Embedded", and the Info pane says checkout will happen in the store's checkout, built over MCP.

Step 1: the profile says the store supports Embedded

Every UCP store publishes a profile at /.well-known/ucp. A store that supports Embedded adds a shopping service with a transport of embedded. Unlike REST and MCP it has no endpoint, because there is nothing to call:

"dev.ucp.shopping": [
  { "version": "2026-08-25", "transport": "rest", "endpoint": "https://flowers-embedded.ucpsandbox.com/ucp" },
  { "version": "2026-08-25", "transport": "mcp",  "endpoint": "https://flowers-embedded.ucpsandbox.com/mcp" },
  { "version": "2026-08-25", "transport": "embedded",
    "schema": "https://ucp.dev/2026-08-25/services/shopping/embedded.openrpc.json" }
]

That is the first of two levels: the profile says the store supports it, and each checkout says whether it applies this time.

Step 2: the agent builds the checkout over MCP

The agent works as it would without Embedded: search_catalog, then create_checkout and update_checkout on the store's MCP service for the buyer, the address and free delivery. The checkout came back ready_for_complete.

The difference is in each answer. Beside the continue_url, the checkout carries an embedded binding in ucp.services, whose config.delegate lists the steps the store will let the host take over:

{
  "id": "01a18e12-78dd-7263-acba-36eb8b3d8ebe",
  "status": "ready_for_complete",
  "continue_url": "https://flowers-embedded.ucpsandbox.com/checkout/01a18e12-78dd-7263-acba-36eb8b3d8ebe",
  "ucp": {
    "version": "2026-08-25",
    "services": {
      "dev.ucp.shopping": [{
        "version": "2026-08-25", "transport": "embedded",
        "config": { "delegate": ["payment.credential", "fulfillment.address_change",
                                 "window.open", "payment.instruments_change"] }
      }]
    }
  }
}

A business MUST include this binding to make a checkout embeddable. Without it, even when the profile lists embedded, the checkout supports redirect only; our test store does this for checkouts containing a White Orchid. Playground's agent never pays: it stops here and hands over.

Step 3: load the store's page with ec_ parameters

The host opens the continue_url in an iframe with parameters prefixed ec_. Only ec_version is required, and it must match the checkout's version; ec_delegate, ec_color_scheme and ec_auth are optional. Playground loaded:

https://flowers-embedded.ucpsandbox.com/checkout/01a18e12-78dd-7263-acba-36eb8b3d8ebe
  ?ec_version=2026-08-25
  &ec_delegate=payment.credential,window.open
  &ec_color_scheme=light

Playground asked for two of the four delegations: the payment credential, and opening links (the store's policies) in a new tab. Address and card choice stayed on the store's page, which opens in the run's third pane, beside the chat and the trace.

The hand-off, live: "Your turn" across the top, the agent's checkout ready to pay in the chat, the trace up to update_checkout with the embedded binding, and the store's own checkout page in the third pane, connected to the host.

Step 4: the handshake

The store's page speaks first with ec.ready, a request the host must answer, listing the delegations it accepted:

{ "jsonrpc": "2.0", "id": "ready_1", "method": "ec.ready",
  "params": { "delegate": ["payment.credential", "window.open"] } }
{ "jsonrpc": "2.0", "id": "ready_1",
  "result": { "ucp": { "version": "2026-08-25", "status": "success" } } }

Delegation narrows at every stage. The spec writes it as a chain:

config.delegate ⊇ ec_delegate ⊇ ec.ready delegate

The store allows, the host asks, the page confirms. An accepted delegation is binding: the page MUST send the request at that step, wait for the answer, and show no UI of its own for it. Playground's Discovery and negotiation tab shows the chain per delegation.

The run's Discovery and negotiation tab: the page opened with ec.ready at 2026-08-25, the binding came in update_checkout, and the delegation chain shows payment.credential and window.open allowed, asked and accepted, while payment.instruments_change and fulfillment.address_change stay with the store's page.

The answer may also carry a MessagePort (a private channel) or an auth credential; Playground sends neither and stays on postMessage.

Step 5: the page reports what the buyer does

Next comes ec.start, a notification with the full checkout. Every edit then produces an ec.*.change notification carrying the whole checkout, not just the changed fields. In this run the agent had already set the address and free delivery, so the buyer typed an email and the recipient's name into the store's page, which sent ec.buyer.change with the checkout's new buyer (email, first and last name), then picked a saved test card, which sent ec.payment.change. Notifications get no answer, and the host never has to poll.

The store's own checkout page in Playground's third pane: "Connected to the host", with the buyer's email and the recipient's name filled in.

Step 6: the store asks the host for the payment credential

The buyer pressed Pay. With payment.credential delegated, the page asked the host and waited:

{ "jsonrpc": "2.0", "id": "payment_credential_request_2",
  "method": "ec.payment.credential_request",
  "params": { "checkout": { "id": "01a18e12-…", "status": "ready_for_complete",
    "payment": { "instruments": [ { "id": "instr_1", "handler_id": "mock_payment_handler", "type": "card",
      "display": { "brand": "Visa", "last_digits": "1234" }, "selected": true } ] } } } }

The host MUST NOT silently release a token based solely on the message. Playground showed its own sheet over the store's page, naming the store and the amount:

Playground's sheet over the store's checkout: "flowers-embedded.ucpsandbox.com asks for your payment card", Visa •••• 1234 test card, USD 35.00, with Cancel and Send test card.

The buyer chose "Send test card" about seven seconds later, and only then did Playground answer, with the selected instrument and its credential (the shared run redacts the token):

{ "jsonrpc": "2.0", "id": "payment_credential_request_2",
  "result": {
    "ucp": { "version": "2026-08-25", "status": "success" },
    "checkout": { "payment": { "instruments": [ {
      "id": "instr_1", "handler_id": "mock_payment_handler", "type": "card",
      "display": { "brand": "Visa", "last_digits": "1234" }, "selected": true,
      "credential": { "type": "token", "token": "success_token" } } ] } } } }

Cancel answers abort_error, which is recoverable: the page stays open. Playground sends test cards to test stores only; on a real store it asks to handle links only, so payment stays on the store's page.

Step 7: the order, and a second check

The store completed the checkout on its own server and sent ec.complete with the order:

{ "jsonrpc": "2.0", "method": "ec.complete",
  "params": { "checkout": { "id": "01a18e12-…", "status": "completed",
    "order": { "id": "01a18e13-3b85-73de-929f-261b944ba225", "label": "NF-TAIK5P",
      "permalink_url": "https://flowers-embedded.ucpsandbox.com/orders/01a18e13-3b85-73de-929f-261b944ba225" } } } }

The store's page showed its own confirmation, "Order placed", inside the frame. Playground doesn't take the page's word for it: it called get_checkout over MCP, got completed with the same order, and only then let the agent confirm to the buyer. The trace shows the exchange as one band, each message with its origin checked.

The order placed: the store's own "Order placed · NF-TAIK5P" in the frame, the paid checkout and the agent's reply in the chat, and the trace's embedded band (load, ec.ready, ec.start, ec.buyer.change, ec.payment.change, ec.payment.credential_request, ec.complete) followed by get_checkout confirming the order.

The security rules both sides follow

Putting one company's page inside another's app needs rules on both sides:

  • The store MUST send a Content-Security-Policy: frame-ancestors header naming the hosts allowed to embed it. Our test store lists Playground (and local development addresses) and nothing else, so no other site can frame it.
  • The host sandboxes the iframe (allow-scripts allow-forms allow-same-origin) and SHOULD load it credentialless, a fresh context with none of the buyer's existing cookies or sessions, so the store can't link the visit to other activity.
  • Both check the origin of every message. Playground accepts messages only from the continue_url's origin.

The run's Info tab records the URL, the frame and the result.

The run's Info tab: checkout transport Embedded, built over MCP; the URL loaded with ec_version, ec_delegate and ec_color_scheme; the sandboxed, credentialless frame; Playground handled the payment credential after the buyer confirmed; six messages from the store's checkout.

What the specification leaves open

Building both sides turned up three places where implementers must choose. None stopped the order, but two implementations could choose differently:

  • What config.delegate means. The spec describes it as "the intersection of what the host requested via ec_delegate and what the business allows", but the host only sends ec_delegate later, when it loads the page. The chain in the same section reads it as what the business allows. We built to the chain.
  • How the page learns the host's origin. The host checks messages against the continue_url's origin, but the spec doesn't say how the page finds the host's origin to address and check its own. Our test store reads it from the browser and talks only to origins in its frame-ancestors list.
  • Redirects inside the frame. Payment redirects and 3-D Secure are allowed, but the recommended sandbox has no popups or top-level navigation. Host and store "MAY negotiate additional capabilities", with no field to do it.

What it means for stores

  • Both levels are needed: embedded in the profile, and the binding in each checkout. Without the binding, hosts fall back to the link.
  • Allow only delegations you will honour. Accepted in ec.ready, they bind your page to wait for the host.
  • Set frame-ancestors. It is a MUST, and it decides which apps can show your checkout.

About 11,000 verified stores in our directory already list embedded in their profiles, almost all on Shopify (transports, 30 October). We haven't tested whether their checkouts load in Playground's frame; frame-ancestors decides that, store by store.

What it means for agent builders

  • Build the checkout over REST or MCP, then look for the binding before handing over.
  • Ask only for delegations you can handle, and answer every request: a cancel is abort_error, not silence.
  • Never release a credential without the buyer's confirmation, and confirm the order with get_checkout.

Try it

Start a run at flowers-embedded.ucpsandbox.com and pick "Store's checkout · Embedded" in the UCP pill, or open it by hand from Inspect's Hand-off tool. To host the page yourself, the Sandbox quickstart has the iframe, every delegation and switches for testing errors.

What we'll watch

  • Whether the three open points above get settled in the spec, and the open pull request that marks the embedded auth fields required (#802).
  • Which hosts start embedding store checkouts, and which stores allow them in frame-ancestors.
  • How many stores that declare embedded also send the binding in their checkouts.

FAQ

What is UCP's Embedded transport?

It lets an app show a store's own checkout page in an iframe or webview and exchange JSON-RPC messages with it, so the buyer finishes in the store's page. For checkout it is called the Embedded Checkout Protocol (ECP). It doesn't replace REST or MCP, which still build the checkout.

How does an app know a checkout can be embedded?

At two levels: the store's UCP profile lists a shopping service with "transport": "embedded", and each checkout response carries an embedded binding in ucp.services with config.delegate. Without the binding, that checkout supports redirect only.

What is delegation in embedded checkout?

The host taking over a step the store's page would otherwise handle, such as the payment credential, the address or links. The store allows a set, the host asks with ec_delegate, and the page confirms in ec.ready.

Can the host pay without asking the buyer?

No. The spec says the host must not release a payment token based only on the store's request; it must show its own confirmation first.

Can I test it without a real store?

Yes. Northlight Flowers on flowers-embedded.ucpsandbox.com is a test store that supports Embedded, and nothing is charged.


About UCP Checker

UCP Checker is the independent validation and observability layer for the Universal Commerce Protocol. We crawl, validate and grade every public UCP manifest we can find, run the merchant directory, the UCP Score, live adoption stats, the vertical guides and the authority-bound vendor map, and track the spec as it evolves so you don't have to — measured from two vantages (what a business declares, and what actually happens when an agent transacts), the same way for everyone, without picking winners.

Sources

Related coverage: How UCP over A2A works, step by step · How UCP works: a real agent buys flowers · UCP transports · What is /.well-known/ucp?

What does a UCP agent find at your store?
Check it once and it gets its own status page.
Check a store

Keep reading

30 Oct 2026 How UCP over A2A Works: Checkout With a Store’s Agent, Step by Step Guides · 9 min 29 Oct 2026 UCP Food Ordering: Test a Pickup Order End to End Guides · 12 min 26 Oct 2026 UCP Return Policy Extension, Explained: Return Windows, Fees and Final Sale for AI Agents Guides · 15 min