Understanding the UCP Embedded Protocol: Secure iframe and WebView Communication
The Embedded Protocol (EP) is a JSON-RPC 2.0 transport binding that enables secure bidirectional messaging between a host application and an embedded business UI inside an iframe or WebView, handling everything from initial handshake to optional MessageChannel upgrades.
The Embedded Protocol defines how a super-app, browser, or native container can embed merchant interfaces and exchange structured messages with them. According to the Universal-Commerce-Protocol/ucp repository, this specification lives in docs/specification/embedded-protocol.md and serves as the foundation for capability-specific implementations like Embedded Checkout and Embedded Cart.
What Is the Embedded Protocol?
The Embedded Protocol (EP) standardizes communication between a host (the embedding application) and an embedded business UI running inside an iframe or native WebView. It specifies the message format, transport mechanisms, and security constraints required for cross-context interoperability.
At its core, EP mandates that all messages conform to the JSON-RPC 2.0 specification, using a strict envelope structure with jsonrpc, method, params, and optional id fields. This guarantees a uniform, language-agnostic format that any host or embedded page can parse, whether implemented in JavaScript, Swift, Java, or Kotlin.
Core Message Architecture
Request and Notification Types
EP distinguishes between two message patterns to control response expectations:
- Requests – JSON-RPC objects containing an
idfield require a synchronous response from the host. These block until the host returns a result or error. - Notifications – Messages without an
idfield operate as fire-and-forget events. The embedded UI uses these to push state changes likeec.line_items.changeorep.cart.startwithout waiting for acknowledgment.
Error Handling Strategy
The protocol separates failure modes into distinct layers. Transport-level failures (network issues, malformed JSON) use the JSON-RPC error field, while application-level outcomes always return inside result.ucp.status. This field contains either success or error, allowing hosts to distinguish between protocol failures and business-logic failures without parsing capability-specific payloads.
Channel Setup and Upgrade Flow
Initial Handshake via postMessage
Communication begins with standard window.postMessage calls between the host window and the iframe content. As implemented in source/schemas/transports/embedded_config.json, the transport configuration schema validates allowed delegation patterns and Content Security Policy requirements before the connection activates.
The embedded context initiates the session by sending an ec.ready request (or equivalent capability-specific ready method). This request includes a params.delegate array listing capabilities—such as payment or address—that the embedded UI wants to delegate to the host.
Optional MessageChannel Upgrade
Upon receiving ec.ready, the host may respond with an upgrade object containing a transferred MessagePort. This optimization, documented in the Embedded Protocol specification, establishes a dedicated MessageChannel for lower-latency communication while maintaining same-origin restrictions.
If both parties agree to the upgrade, they switch from postMessage to the transferred port and resend the ready handshake over the new channel. If the host declines the upgrade, communication continues over the standard postMessage bus.
Native WebView Bridges
For native iOS or Android hosts, the protocol exposes globals on the embedded page's window object. Native code listens on window.Embedded{Capability}ProtocolConsumer or falls back to window.webkit.messageHandlers.Embedded{Capability}ProtocolConsumer to receive JSON-RPC strings directly from the WebView context.
Security Constraints
The Embedded Protocol enforces strict security at multiple layers to prevent click-jacking and cross-origin data leakage:
- Origin validation – Hosts must verify
event.originagainst the iframe'scontinue_urlbefore processing any message. - CSP frame-ancestors – The specification mandates Content Security Policy rules restricting which domains may embed the interface.
- Sandboxed iframes – Embedded contexts should run in sandboxed environments with limited privileges.
- Credentialless attribute – Optional support for the
credentiallessiframe attribute prevents credential leakage during cross-origin loads.
Implementation Examples
Host Side: Listening and Upgrading
This example from the UCP reference implementation shows a host receiving the initial ready request and optionally upgrading to a MessageChannel:
// Host page (super-app implementation)
const iframe = document.getElementById('businessIframe');
window.addEventListener('message', async (event) => {
// Strict origin validation against incoming event
if (event.origin !== new URL(iframe.src).origin) return;
const msg = JSON.parse(event.data);
if (msg.method === 'ec.ready') {
// Create dedicated channel for improved performance
const { port1, port2 } = new MessageChannel();
const response = {
jsonrpc: '2.0',
id: msg.id,
result: {
ucp: { version: '1.0', status: 'success' },
upgrade: { port: port2 },
delegate: ['payment', 'address']
}
};
// Transfer port2 to the embedded context
iframe.contentWindow.postMessage(
JSON.stringify(response),
event.origin,
[port2]
);
// Use port1 for subsequent communication
port1.onmessage = (e) => handleEmbeddedMessage(e.data);
}
});
Embedded Context: Sending Ready and Handling Upgrade
The embedded business page initiates the session and handles the optional channel transfer:
function sendReady() {
const ready = {
jsonrpc: '2.0',
id: 'ready_1',
method: 'ec.ready',
params: {
delegate: ['payment', 'address']
}
};
window.parent.postMessage(JSON.stringify(ready), '*');
}
window.addEventListener('message', (event) => {
const resp = JSON.parse(event.data);
if (resp.result?.upgrade?.port) {
const port = resp.result.upgrade.port;
port.onmessage = (e) => handleHostMessage(e.data);
// Re-send ready over the dedicated channel
port.postMessage(JSON.stringify({
jsonrpc: '2.0',
id: 'ready_2',
method: 'ec.ready',
params: { delegate: ['payment', 'address'] }
}));
} else {
handleHostMessage(resp);
}
});
sendReady();
State Change Notifications
Cart state updates use fire-and-forget notifications without response requirements:
function notifyCartChange(cart) {
const notif = {
jsonrpc: '2.0',
method: 'ep.cart.change',
params: { cart }
};
// Prefer protocol-specific global if available
const target = window.EmbeddedCartProtocol || window.parent;
target.postMessage(JSON.stringify(notif), '*');
}
Session Error Escalation
When encountering unrecoverable errors, the embedded context sends a notification containing a continue_url so the host can redirect the user safely:
function sendSessionError() {
const err = {
jsonrpc: '2.0',
method: 'ec.error',
params: {
ucp: { version: '1.0', status: 'error' },
messages: [{
type: 'error',
code: 'not_supported_error',
content: 'Requested auth credential type is not supported.',
severity: 'unrecoverable'
}],
continue_url: 'https://merchant.example.com/checkout'
}
};
window.parent.postMessage(JSON.stringify(err), '*');
}
Summary
- The Embedded Protocol standardizes JSON-RPC 2.0 messaging between host applications and embedded iframes/WebViews in the UCP ecosystem.
- Message types split into blocking Requests (with
id) and non-blocking Notifications (withoutid) to optimize performance. - Channel flexibility allows starting with
postMessageand optionally upgrading to a transferredMessageChannelfor dedicated communication. - Security layers include strict origin validation, CSP frame-ancestors, and sandboxed iframe policies to prevent cross-origin attacks.
- Error separation distinguishes transport failures (JSON-RPC error field) from business outcomes (
result.ucp.statusfield). - Capability-specific bindings like
ec.*for checkout andep.cart.*for cart functionality extend the base protocol defined indocs/specification/embedded-protocol.md.
Frequently Asked Questions
How does the Embedded Protocol handle security for cross-origin iframe communication?
The protocol mandates strict origin validation on every postMessage event, requiring hosts to verify event.origin against the embedded page's continue_url. Additionally, the specification recommends CSP frame-ancestors restrictions, sandboxed iframe attributes, and optional credentialless flags to prevent credential leakage and click-jacking attacks.
What is the difference between Requests and Notifications in EP?
Requests contain an id field and require the host to return a JSON-RPC response, creating a synchronous blocking pattern suitable for authentication or payment authorization. Notifications omit the id field and operate as fire-and-forget messages, allowing the embedded UI to broadcast state changes like cart updates without waiting for host acknowledgment.
When should applications use the MessageChannel upgrade instead of postMessage?
Applications should upgrade to MessageChannel when both the host and embedded context require lower-latency communication or want to isolate traffic from the standard window message bus. The upgrade occurs during the initial handshake when the host responds to ec.ready with a transferred MessagePort, though the protocol gracefully falls back to postMessage if either side doesn't support the upgrade.
Where are the authoritative specifications for the Embedded Protocol located?
The core specification resides at docs/specification/embedded-protocol.md in the Universal-Commerce-Protocol/ucp repository. Capability-specific extensions are documented separately: checkout methods use docs/specification/embedded-checkout.md, while cart functionality is defined in docs/specification/embedded-cart.md. The transport configuration schema at source/schemas/transports/embedded_config.json validates runtime EP implementations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →