Payment Page Message Protocol
Use this page if you connect the embedded payment page to your page yourself instead of using the snippet from Merchant Hosted. The exchange runs over window.postMessage.
Envelope
Every message, service or application, is wrapped in the same envelope — the examples below show it in full:
| Field | Description |
|---|---|
channel | Both sides must use the same value, by default payment-page; messages with a different channel are ignored |
protocolVersion | Currently 1.0.0 |
messageId | A UUID identifying the message |
messageType | Service types are prefixed with @ (@ready, @init, @ack), application types are not (close-request, payment-event) |
payload | The message body; its shape depends on messageType |
Handshake
The payment page sends @ready on start and repeats it every 300 ms until your page answers with @init. If no @init arrives within 3 seconds, the payment page treats the channel as unavailable: it shows no close button and sends no events.
The payment page announces itself:
{
"channel": "payment-page",
"protocolVersion": "1.0.0",
"messageId": "...",
"messageType": "@ready",
"payload": {
"capabilities": { "close": true, "paymentEvent": true },
"supportedVersion": "1.0.0"
}
}
Your page answers:
{
"channel": "payment-page",
"protocolVersion": "1.0.0",
"messageId": "...",
"messageType": "@init",
"payload": {
"capabilities": { "close": true, "paymentEvent": true },
"supportedVersion": "1.0.0"
}
}
The capabilities in your @init control what the payment page does: it shows the close button only for close: true and sends payment events only for paymentEvent: true. Announce a capability only if you handle the corresponding message; the capabilities announced in @ready can be ignored. Send supportedVersion as 1.0.0 — versions are not negotiated.
Application Messages
close-request reports that the payer clicked the close button. Removing the iframe is your decision:
{
"channel": "payment-page",
"protocolVersion": "1.0.0",
"messageId": "...",
"messageType": "close-request",
"payload": { "reason": "user" }
}
payment-event reports the outcome on a final screen. See Callback Payloads for the fields:
{
"channel": "payment-page",
"protocolVersion": "1.0.0",
"messageId": "...",
"messageType": "payment-event",
"payload": {
"event": "declined",
"final": true,
"orderId": 12345,
"traceId": "550e8400-e29b-41d4-a716-446655440000"
}
}
Acknowledgements
Answer every application message with @ack, quoting the messageId of the message received. Service messages (@ready, @init, @ack) are not acknowledged.
{
"channel": "payment-page",
"protocolVersion": "1.0.0",
"messageId": "...",
"messageType": "@ack",
"payload": { "messageId": "id of the message being acknowledged" }
}
An @ack confirms delivery, not processing. Neither side retries a message or waits for an @ack.
Origin Handling
Accept a message only if it comes from your iframe (event.source === iframe.contentWindow) and from the payment page origin (event.origin === new URL(iframe.src).origin), and drop anything else. Send your messages to that same origin rather than to *.