Embedded checkout

The Embedded checkout integration enables your web-based checkout to be embedded in Google surfaces. Use this path if your product requires complex logic (e.g., customizations) that the Native API cannot support. You will implement a checkout UI that will be embedded in the checkout flow through an iframe.

What is embedded checkout?

Embedded checkout (EC) allows a host (like Google Search or an AI Agent) to display your existing web-based checkout within their application (using an iframe or webview). Unlike a standard web redirect, this allows for bi-directional communication. The host can "delegate" specific tasks such as selecting a saved address or paying with a stored credential to provide a faster, native-feeling experience, while you remain the Merchant of Record and handle the actual order creation.

Merchant implementation checklist

To support Embedded Checkout, you must implement the following requirements across your UCP API and your frontend checkout application.

1. Enable discovery (server)

You must declare that your services support the embedded protocol in your UCP profile.

  • Action: Add the embedded object to your /.well-known/ucp service definition.
  • Requirement: Include the OpenRPC schema URL.
  • Version: Ensure the version matches your UCP service version.
"services": {
  "dev.ucp.shopping": {
    "version": "2026-01-11",
    "spec": "https://ucp.dev/specification/overview",
    "embedded": {
      "schema": "https://ucp.dev/services/shopping/embedded.openrpc.json"
    }
  }
}

2. Handle URL initialization (frontend)

When the host loads your continue_url, they will append specific query parameters. Your frontend must parse these immediately upon loading.

  • Action: Parse the following URL query parameters:
    • ec_version: The protocol version (e.g., 2026-01-11).
    • ec_auth: (Optional) A business-defined token for session validation.
    • ec_delegate: A comma-separated list of actions the host wants to handle natively (e.g., payment.credential, fulfillment.address_change, payment.instruments_change).

3. Establish communication (frontend)

Communication uses JSON-RPC 2.0 using postMessage.

  • Action: Implement a listener for message events.
  • Requirement: You must validate the origin of every message to ensure it matches the host.
  • Native support: If postMessage is unavailable in a native app context, utilize window.EmbeddedCheckoutProtocolConsumer or window.webkit.messageHandlers.EmbeddedCheckoutProtocolConsumer.

4. Perform the handshake (frontend)

Send a request to the host to confirm readiness and accepted delegations. This is a request that requires a response.

  • Action: Send the ec.ready request with an id.
  • The "Two Handshakes" Rule: You may need to perform this handshake twice.
    • First Handshake: Sent using standard window.postMessage. This is used to "check-in" with the host.
    • Channel Upgrade: The host may respond with an upgrade object containing a MessagePort. This creates a private, dedicated communication channel between you and the host.
    • Second Handshake: If an upgrade is provided, you must send a new ec.ready request over the new port to confirm the secure channel is active.
    • Data Retrieval: The host responds to this second request with the result.checkout object. This is where you finally receive the payment instruments to populate your UI.
  • Payload: Include a delegate array listing the capabilities you accept.
// Example: Handshake with Port Upgrade logic
const hostWindow = window.parent;

function sendHandshake(target, id = "ready_1") {
  target.postMessage(JSON.stringify({
    "jsonrpc": "2.0",
    "id": id,
    "method": "ec.ready",
    "params": {
      "delegate": ["payment.credential", "fulfillment.address_change"]
    }
  }), "*");
}

sendHandshake(hostWindow);

window.addEventListener("message", (event) => {
  const response = JSON.parse(event.data);

  if (response.id === "ready_1" && response.result?.upgrade?.port) {
    const privatePort = response.result.upgrade.port;
    privatePort.start();
    sendHandshake(privatePort, "ready_1");
  }
});

5. Implement delegation logic (frontend)

When a delegation is accepted, you must hide your internal UI and request data from the host when needed.

  • Action: Hide the relevant UI elements for delegated tasks.
  • Requirement: Update your checkout state using a PUT-style replacement (replace the whole object section) based on the data returned by the host. Even if a field is delegated, you must still include it in the checkout object for every notification to ensure the host has a "single source of truth" for the entire state of the checkout.
// Example: Requesting a payment credential (token) from the host
communicationChannel.postMessage(JSON.stringify({
  "jsonrpc": "2.0",
  "id": "req_token_1",
  "method": "ec.payment.credential_request",
  "params": {
      "checkout": ... // pass the credential data that needs to be updated
  }
}));

communicationChannel.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.id === "req_token_1" && msg.result) {
      localCheckout.payment.credentials = msg.result.checkout.payment.credentials;
      renderUI();
  }
};

6. Send lifecycle & state updates (frontend)

Inform the host of changes within the iframe using notifications (no id).

  • Action: Send notifications when state changes:
    • ec.start: When the checkout is fully visible.
    • ec.line_items.change: If cart contents (quantity changed, items added/removed) in the checkout UI.
    • ec.buyer.change: If buyer details update.
    • ec.complete: When the order is successfully placed.
    • ec.message.change: Checkout messages have been updated. Messages include errors, warnings, and informational notices about the checkout state.

7. Enforce security (server/headers)

You must ensure your checkout cannot be embedded by malicious actors.

  • Action: Implement Content Security Policy (CSP) headers.
  • Requirement: Set frame-ancestors <host_origin>; to allow embedding only by trusted hosts. Every postMessage received must have its origin strictly validated against the expected host before processing.
  • Navigation: Block logic that navigates the user away from the checkout flow (e.g., remove "Continue Shopping" links that lead to your homepage). Exceptions are allowed for 3DS verification or 3rd party payment redirects.
  • Iframe Context: Your application must be compatible with the sandbox attribute (allow-scripts allow-forms allow-same-origin) and the credentialless attribute, which creates an ephemeral, private session.
  • Prohibition: You must never trigger a payment token request programmatically; the host will only release a token if there is a valid User Activation.