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
Coming soon! If you’re not using the SDK, MoonPay libraries can help you managepostMessage 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 overpostMessage 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. Thekind 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. 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. |
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:
postMessagedelivers strings; treat JSON parsing as fallible and handle errors. - Verify the envelope: reject messages that don’t match the expected
version, don’t include ameta.channelId, or use an unknownkind. - Route by
channelId: if you can have multiple frames open at once, usemeta.channelIdto 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. In practice, you’ll typically: generate a channel, wait for the handshake, ack it, then start handlingkind 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).