Iframe Embedding
Before you start
Origin allow-list requiredBy default the checkout responds with
Unauthorized — this page cannot be embedded from this originwhen loaded inside an<iframe>. Send us the origin (or list of origins) you plan to embed from and we'll allow them.
Referrer requiredThe checkout resolves where to post events from the
Refererheader of the first iframe load. If your page setsReferrer-Policy: no-referrer(orreferrerpolicy="no-referrer"on the iframe tag), the form works but no events are posted at all — silently.
Same customization as the standalone checkoutForm appearance, locale, payment methods and branding are configured the same way as the standalone checkout — see Hosted Checkout customization.
Embedding
<iframe
src="https://checkout.8B.world/s/{sessionId}"
width="100%"
height="720">
</iframe>Inside an iframe:
- All outbound links open in a new tab.
- The Back to store button emits a postMessage (
SESSION_REDIRECTwith your back-to-store URL) instead of navigating — your page performs the actual navigation.
Listening to events
The iframe posts messages to the parent window. Every message shares the same envelope; only payload varies between events:
{
type: string; // event identifier — see the Events section
sessionId: string; // checkout session the form is rendering
intentId: string; // intent the session belongs to
payload?: object; // event-specific, see each event
}sessionId carries its entity prefix (IS…), while intentId is the bare number — the same intent is addressed as I{intentId} in the REST API.
Delivery is at-least-once: the same event can arrive several times, and SESSION_PROCESSING repeats periodically while the payment is pending. Deduplicate by type + sessionId and keep handlers idempotent.
PaymentInfo is used as the payload by several events:
{
paymentId: string;
paymentMethod: FormPaymentMethodType; // e.g. "CARD"
paymentStatus: PaymentStatus; // e.g. "ACCEPTED"
}Events
Every event carries sessionId and intentId at the top level. Only payload varies between events.
type | sessionId | intentId | payload | Fires when |
|---|---|---|---|---|
SESSION_METADATA_SENT | ✓ | ✓ | — | The iframe is ready. Sent in some flows only — do not rely on it in every session. |
CHECKOUT_LOADED | ✓ | ✓ | — | The payment-method screen rendered. Not emitted when the session opens in any other state (processing, finished, expired). |
type | sessionId | intentId | payload | Fires when |
|---|---|---|---|---|
PAYMENT_METHOD_SELECTED | ✓ | ✓ | { paymentMethod } | Emitted once when the payment screen loads, with the default (first) method — switching methods does not re-emit it. |
PAYMENT_ATTEMPT_SUBMITTED | ✓ | ✓ | { paymentMethod } | User clicked Pay. |
PAYMENT_ATTEMPT_SUCCESS | ✓ | ✓ | PaymentInfo | Payment record was created. |
PAYMENT_ATTEMPT_FAILED | ✓ | ✓ | { paymentMethod, reason } | The pay request was rejected and no payment was created — issuer declines do not arrive here. reason is a free-text message, not a stable code. |
PAYMENT_FAILED | ✓ | ✓ | PaymentInfo | Attempt declined or errored while the session is still active — the payer can retry. If the failure closes the session, SESSION_FAILED fires instead. |
PAYMENT_EXPIRED | ✓ | ✓ | { paymentMethod } | Reserved — not emitted today. An expired QR / payment attempt currently produces no event. |
type | sessionId | intentId | payload | Fires when |
|---|---|---|---|---|
SESSION_REDIRECT | ✓ | ✓ | { url } | The payer should follow url: a 3DS challenge (opens in a new tab) or a Back to store click (url is your back-to-store URL). |
SESSION_PROCESSING | ✓ | ✓ | PaymentInfo | Awaiting final state (typical for cards). May repeat periodically until a terminal event arrives. |
SESSION_SUCCESS | ✓ | ✓ | PaymentInfo | Payment completed and session reached final state. |
SESSION_FAILED | ✓ | ✓ | — | Terminal failure — the session is closed, no further attempts in it. An issuer decline typically ends here. |
SESSION_CANCELLED | ✓ | ✓ | — | The session was cancelled via the Cancel Session API while the form was open. |
SESSION_ERROR | ✓ | ✓ | — | Reserved for unrecoverable technical errors — not emitted today. |
SESSION_EXPIRED | ✓ | ✓ | — | Session lifetime elapsed. |
Which failure event will I get?
PAYMENT_ATTEMPT_FAILED— the pay request was rejected up front: nothing was charged and no payment exists. An issuer decline never arrives through it.PAYMENT_FAILED— a payment failed but the session stays open, so the payer can retry in the same form.SESSION_FAILED— the session finished with a failure and is closed; an issuer decline typically surfaces here. Handle all three, and fetch the decline reason via the REST API — the events themselves don't carry it.
Examples
Success
{
"type": "PAYMENT_ATTEMPT_SUCCESS",
"sessionId": "IS313605683903180800",
"intentId": "313605683324366848",
"payload": {
"paymentId": "313631993211891712",
"paymentMethod": "CARD",
"paymentStatus": "ACCEPTED"
}
}Failure
{
"type": "SESSION_FAILED",
"sessionId": "IS313644775239876608",
"intentId": "313644775160184832"
}{
"type": "PAYMENT_ATTEMPT_FAILED",
"sessionId": "IS313639612777435136",
"intentId": "313639612693549056",
"payload": {
"paymentMethod": "CARD",
"reason": "FX rate expired or not found for payment method BankCardKZ in session IS313639612777435136"
}
}Updated 37 minutes ago
