# Understanding the UCP Embedded Protocol: Secure iframe and WebView Communication

> Learn about the UCP Embedded Protocol, a secure JSON-RPC 2.0 transport for bidirectional iframe/WebView communication. Securely integrate business UIs with your host application.

- Repository: [Universal Commerce Protocol (UCP)/ucp](https://github.com/Universal-Commerce-Protocol/ucp)
- Tags: deep-dive
- Published: 2026-04-26

---

**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`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/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 `id` field require a synchronous response from the host. These block until the host returns a result or error.
- **Notifications** – Messages without an `id` field operate as fire-and-forget events. The embedded UI uses these to push state changes like `ec.line_items.change` or `ep.cart.start` without 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`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/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.origin` against the iframe's `continue_url` before 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 `credentialless` iframe 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:

```javascript
// 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:

```javascript
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:

```javascript
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:

```javascript
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 (without `id`) to optimize performance.
- **Channel flexibility** allows starting with `postMessage` and optionally upgrading to a transferred `MessageChannel` for 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.status` field).
- **Capability-specific bindings** like `ec.*` for checkout and `ep.cart.*` for cart functionality extend the base protocol defined in [`docs/specification/embedded-protocol.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/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`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/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`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/embedded-checkout.md), while cart functionality is defined in [`docs/specification/embedded-cart.md`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/docs/specification/embedded-cart.md). The transport configuration schema at [`source/schemas/transports/embedded_config.json`](https://github.com/Universal-Commerce-Protocol/ucp/blob/main/source/schemas/transports/embedded_config.json) validates runtime EP implementations.