> ## Documentation Index
> Fetch the complete documentation index at: https://dev.moonpay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Overview

> Protocol details for co-branded and headless [frames](/platform/overview/core-concepts#frames).

Frames communicate with your app using `postMessage`. Each frame defines its own events and payloads. If you use the MoonPay SDK, it handles message serialization, validation, and dispatch for you. If you integrate frames directly (for example, with your own WebView or iframe wrapper), this page documents the shared protocol so you know what to implement.

## Libraries

<Badge size="xs" color="purple" className="px-2 ml-1" shape="pill">
  Coming soon!
</Badge>

If you’re not using the SDK, MoonPay libraries can help you manage `postMessage` communication on web and mobile (via WebViews). Until those ship, use the protocol details below to build your own bridge.

## Frames protocol

### Messages

Frames use an event-driven model. You and the frame exchange events using a strongly typed message structure. Treat these messages like an API contract between two parties: the parent window (or app) and the frame.

#### Transport

In both web and mobile apps, frames send and receive messages over `postMessage` as **stringified JSON**.

If you integrate directly, you handle serialization and validation yourself. If you use the SDK, it handles this for you.

#### Format

Every message follows the same envelope format. The `kind` tells you what event you’re handling, and the `payload` shape depends on that `kind`.

| Field            | Type                   | Required | Description                                                                           |
| ---------------- | ---------------------- | -------- | ------------------------------------------------------------------------------------- |
| `version`        | `2`                    | ✅        | The frames protocol version.<br /><br />This value will always be `2`.                |
| `meta`           | `object`               | ✅        | Transport metadata for the message.                                                   |
| `meta.channelId` | `string`               | ✅        | A unique identifier for messages between frames.                                      |
| `kind`           | `enum<FrameEventKind>` | ✅        | The name of the event.                                                                |
| `payload`        | `object`               |          | An object containing the data for different events. This value depends on the `kind`. |

<CodeGroup>
  ```json Example payload (deserialized) theme={null}
  {
    "version": 2,
    "meta": {
      "channelId": "some_unique_value"
    },
    "kind": "example",
    "payload": {
      "example": "an example payload"
    }
  }
  ```

  ```json Example payload (serialized) theme={null}
  "{\"version\":\"2\",\"meta\":{\"channelId\":\"some_unique_value\"},\"kind\":\"example\",\"payload\":{\"example\":\"an example payload\"}}"
  ```
</CodeGroup>

#### Validation and safety checks

You’ll have an easier time (and fewer mysterious bugs) if you validate messages like you would any external input:

* **Check the origin and sender**: only accept messages from the frame origin(s) you expect, and ignore everything else.
* **Parse defensively**: `postMessage` delivers strings; treat JSON parsing as fallible and handle errors.
* **Verify the envelope**: reject messages that don’t match the expected `version`, don’t include a `meta.channelId`, or use an unknown `kind`.
* **Route by `channelId`**: if you can have multiple frames open at once, use `meta.channelId` to keep messages from crossing streams.

### Lifecycle

Each frame follows the same basic handshake lifecycle to establish a bi-directional channel with your app. The SDK manages this automatically and gives you an events callback; in a direct integration, you implement these steps yourself.

```mermaid theme={null}
sequenceDiagram
    %% autonumber

    participant a as App
    participant f as Frame

    %% -----------------

    activate f
      a ->> a: Generate a channel ID
      a ->> f: Inject the WebView or iframe with<br />URL params including a channelId.

      alt If no handshake request received in 5s
        a -x a: Handle loading error
      end

      f ->> a: Send handshake w/channel ID
      a ->> f: Reply with ack (w/channel ID)

      f ->> f: Validate params

      break Invalid params
        f -x a: Send error and terminate the connection
      end


        f <<->> a: Bi-directional channel opened


    deactivate f
```

In practice, you’ll typically: generate a channel, wait for the handshake, ack it, then start handling `kind` events for that channel. If the handshake doesn’t arrive quickly, fail fast and show a useful error to the developer (or retry, if that fits your app).
