Skip to main content

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:

FieldDescription
channelBoth sides must use the same value, by default payment-page; messages with a different channel are ignored
protocolVersionCurrently 1.0.0
messageIdA UUID identifying the message
messageTypeService types are prefixed with @ (@ready, @init, @ack), application types are not (close-request, payment-event)
payloadThe 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 *.