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/ucpservice 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
originof every message to ensure it matches the host. - Native support: If
postMessageis unavailable in a native app context, utilizewindow.EmbeddedCheckoutProtocolConsumerorwindow.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.readyrequest with anid. - 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.readyrequest over the new port to confirm the secure channel is active. - Data Retrieval: The host responds to this second request with the
result.checkoutobject. 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. EverypostMessagereceived must have itsoriginstrictly 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
sandboxattribute (allow-scripts allow-forms allow-same-origin) and thecredentiallessattribute, 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.