Hosted Payment Page Iframe Integration
Embed the Hosted Payment Page in an iframe and communicate over the postMessage lifecycle protocol.
The Hosted Payment Page (HPP) supports two embedding modes: redirect mode (you navigate the cardholder to the HPP URL) and iframe mode (you embed the HPP inside a parent page and communicate over the postMessage lifecycle protocol). This guide covers iframe mode: authorizing a parent origin, the lifecycle event protocol, the command channel, and the security model.
Two pieces of configuration
Iframe embedding requires two independent settings to line up:
- Page-level
AllowedEmbeddingDomains: a list of domains (max 20) authorized to embed a given HPP. Wildcards such as*.example.comare supported. Raw IP addresses,localhost, and entries without a TLD are rejected. This is the security envelope: a page without your origin in its allow list cannot be framed by your site. - Per-session
parentOrigin: set when creating each session. It must be a valid HTTPS origin whose host matches anAllowedEmbeddingDomainsentry. If it is omitted, the session is still valid for non-embedded use, but the postMessage channel is disabled fail-closed (no events flow to a parent).
If the host does not match, session creation returns 400 with HostedPaymentPage:HppSession:ParentOriginNotAllowed.
The postMessage lifecycle protocol
Once a session has a parentOrigin, the embedded HPP emits a stream of postMessage events to the parent throughout the session lifecycle. Every event shares one envelope shape; the data payload varies by type. Filter on the source discriminator (winkpg-hpp).
A successful payment fires events in this order:
session_loaded -> ready -> payment_started -> payment_succeeded -> navigated(to: 'result')
Subscribe to ready (not session_loaded) when you need the iframe to be visually interactive: ready fires once the form root has mounted in the DOM.
Key events include payment_succeeded, payment_failed, payment_pending, height_changed (for auto-resize), validation_failed, session_expired, session_invalid, and card_saved (for save-card-only sessions). If no event arrives within a few seconds, fall back to a timeout and treat it like session_invalid: a session that never existed has no parentOrigin to address, so it is intentionally silent.
What the parent receives on a payment
Payment payloads are restricted to fields the public session endpoint already exposes. No PAN, CVV, expiration, network token, wallet cryptogram, or PII is ever included. A successful payment carries the transaction id, requested and approved amounts (compare them to detect a partial approval), masked last4 and brand, the auth code, and the result status. Token fields (paymentTokenPublicReference, customerId, schemeTransactionId) are present only when the cardholder saved a card; key any card-on-file workflow off the presence of paymentTokenPublicReference.
Locking the amount
A session decides who sets the amount the shopper pays. There are three choices. Customer-entered puts the shopper in control: they type the figure themselves, which suits an open balance or an ad-hoc payment. Suggested pre-fills a figure the shopper can still change, which suits a suggested donation or a recommended top-up. Locked fixes the figure so the shopper cannot change it, which suits an invoice or a known order total.
On a locked session, the read-only field is the visible part of the guarantee, not the whole of it. The amount set when the session is created is held with the session on WinkPG's servers, and every submitted payment is checked against it before anything is authorized. A submission whose amount does not match the locked value is rejected, so editing the page in a browser, replaying the request with a different figure, or calling the submit endpoint directly all reach the same outcome: the shopper pays the amount you set, or nothing is charged. The check does not depend on how the shopper pays, so card entry and digital wallets such as Apple Pay and Google Pay are all covered.
A locked session fixes the whole payable total, including any tax, shipping, or convenience fee supplied with the session. The clearest setup is a single figure: fold tax and shipping into the locked amount and send one total. The page then shows the shopper exactly what will be charged, and there is one number to reconcile against the payment afterwards.
Two-way command channel
The parent can send a small, strictly allowlisted set of commands back into the iframe: cancel, set_locale, prefill, and request_height. Inbound commands carry a distinct discriminator (winkpg-hpp-cmd) so an outbound envelope cannot be replayed back into the iframe as a command. There is no programmatic submit: payment authorization stays user-initiated.
Security model
A receiver written against the raw addEventListener('message', ...) API must do all three checks before trusting an event:
window.addEventListener('message', (event) => {
if (event.origin !== 'https://hpp.winkpg.com') return; // (1) origin
const msg = event.data;
if (!msg || msg.source !== 'winkpg-hpp') return; // (2) source discriminator
if (msg.sessionId !== mySessionId) return; // (3) session scope
// handle msg.type
});
Skipping any one of these weakens the model. Prefer the client helper library, which performs these checks and exposes typed on(...) and send(...) APIs.
Quick-start checklist
- Confirm the HPP page has the embedding feature enabled and add your origin to
AllowedEmbeddingDomains. - Set
parentOriginto your exact origin on each session create. - Embed the iframe using the session response URL and serve the parent over HTTPS.
- Register handlers for at least
ready,payment_succeeded,payment_failed,session_invalid, andheight_changed. - Implement a "no event within N seconds" timeout fallback.
See also
- The Embedded Payments SDK: the supported browser loader over this protocol, which performs the origin, source, and session checks above and exposes the lifecycle as callbacks.
- Setting Up a Hosted Payment Page: choose a page mode, walk the creation wizard, and configure page options before you get to embedding it.