Element events
The checkout iframe communicates with the parent page via window.postMessage. This is the raw protocol; SDK users usually consume higher-level controller events instead.
This page documents the raw postMessage contract between
link.cimplify.io/elements/checkout and the parent page. The source of truth is
IframeToParentMessage and ParentToIframeMessage in @cimplify/sdk.
For agents: if you use <CimplifyCheckout> or createElements, the SDK
handles these messages for you. Only implement this page directly when embedding
the raw checkout iframe.
Origin and Routing
Iframe messages include a per-element nonce URL parameter. The parent should:
- Verify
event.originis the expected Link origin. - Verify
event.sourcematches the iframe'scontentWindow. - Verify
event.data.noncematches the iframe URL nonce.
The SDK accepts https://cimplify.io, https://*.cimplify.io, and localhost
for development. A raw production integration should normally pin to
https://link.cimplify.io.
const LINK_ORIGIN = "https://link.cimplify.io";
window.addEventListener("message", (event) => {
if (event.origin !== LINK_ORIGIN) return;
if (event.source !== iframe.contentWindow) return;
if (event.data?.nonce !== nonce) return;
// handle trusted message
});Iframe to Parent
ready
First message after iframe load. Reply with init.
{ type: "ready", height: 412, nonce: "ab12cd34" }height_change
Content resized. Apply it to the iframe height.
{ type: "height_change", height: 564, nonce: "ab12cd34" }auth_requested
The standalone auth element needs the parent to authenticate through
Cimplify OAuth. Checkout no longer sends this — its OTP runs in-panel and
identity arrives via session_established.
{
type: "auth_requested",
loginHint: "jane@example.com",
contactType: "email",
nonce: "ab12cd34"
}SDK behavior: silent-first OAuth with loginHint as login_hint;
prompt: "login" skips silent auth so the shopper can change accounts.
Raw iframe behavior: start OAuth yourself and send identity with the
session token and the access token your callback returns.
session_established
Checkout verified the shopper's OTP in-panel and already hydrated their saved details. Checkout has already bound the storefront session to the verified identity on the server (the session token rotated once, the cart folded under the customer). The parent's job is adoption: take the bound session token, the access token and the customer, and persist the refresh token first-party. The parent never claims or refreshes for the iframe.
{
type: "session_established",
sessionToken: "…",
accessToken: "…",
refreshToken: "…",
customer: { id: "…", name: "…", email: null, phone: null }
}Checkout does not wait for a reply — ignoring this message costs only cross-visit recognition, never the current checkout.
continue_without_saved_details
The shopper chose to proceed as a guest after recognition (or a challenge was abandoned). The parent clears any identity it holds for this storefront session.
{
type: "continue_without_saved_details",
contact: "jane@example.com",
contactType: "email"
}contact_provided
The shopper supplied a guest contact. The SDK stores it as checkout customer data, but it is not authenticated identity.
{
type: "contact_provided",
contact: "jane@example.com",
contactType: "email"
}address_changed / address_selected
{
type: "address_changed",
address: {
street_address: "12 Independence Ave",
city: "Accra",
region: "Greater Accra",
country: "GH"
},
saveToLink: true
}address_selected carries the same address shape and is emitted when a saved
address is chosen.
payment_method_selected
{
type: "payment_method_selected",
method: {
type: "mobile_money",
provider: "mtn",
phone_number: "+233200000001"
},
saveToLink: true
}order_type_changed
{ type: "order_type_changed", orderType: "delivery" }request_submit
The iframe Pay button was pressed. Respond with process_checkout or call
elements.processCheckout(...).
{ type: "request_submit" }checkout_status
Non-terminal checkout transition.
{
type: "checkout_status",
status: "awaiting_authorization",
context: {
display_text: "Approve the prompt on your phone",
authorization_type: "otp",
provider: "mtn"
}
}checkout_complete
Terminal checkout result.
{
type: "checkout_complete",
success: true,
order: {
id: "ord_…",
order_number: "10042",
status: "confirmed",
total: "29.99",
currency: "GHS"
},
enrolled_in_link: true
}{
type: "checkout_complete",
success: false,
error: {
code: "PAYMENT_DECLINED",
message: "The provider declined the payment.",
recoverable: true
}
}error
{ type: "error", code: "AUTH_CONFIG_REQUIRED", message: "OAuth clientId and redirectUri are required." }logout_complete
The iframe completed logout or cleared local auth state.
{ type: "logout_complete" }Parent to Iframe
init
Sent after ready.
{
type: "init",
businessId: "biz_…",
publicKey: "cpk_live_…",
prefillEmail: "jane@example.com",
appearance: { theme: "light" },
orderTypes: ["delivery", "pickup"],
defaultOrderType: "delivery",
renderSubmitButton: true,
submitLabel: "Pay now",
token: "eyJ…"
}identity
Hands the iframe the identity the parent holds: the storefront session token
and, when the shopper is signed in, the access token and customer. The SDK
sends it on mount and whenever its identity changes; this is also how a
hand-rolled parent completes an auth_requested flow.
{
type: "identity",
sessionToken: "…",
accessToken: "eyJ…",
customer: { id: "…", name: "…", email: null, phone: null }
}An anonymous parent sends accessToken: null and customer: null.
set_cart
{
type: "set_cart",
cart: {
items: [{ name: "Jollof bowl", quantity: 1, unit_price: "29.99", total_price: "29.99", line_type: "simple" }],
subtotal: "29.99",
tax_amount: "0.00",
total_discounts: "0.00",
service_charge: "0.00",
total: "29.99",
currency: "GHS"
}
}process_checkout
{
type: "process_checkout",
cart_id: "cart_…",
order_type: "delivery",
location_id: "loc_…",
enroll_in_link: true,
customer: { name: "Jane", email: "jane@example.com", phone: null },
address: { street_address: "12 Independence Ave", city: "Accra", region: "Greater Accra" }
}Other Commands
| Message | Purpose |
|---|---|
get_data | Ask the iframe to emit its current selected data where supported. |
logout | Clear iframe auth state. |
abort_checkout | Abort an in-flight checkout. |
SDK Events vs Raw Messages
The SDK re-emits a smaller event set from CimplifyElement:
| SDK event | Source raw message |
|---|---|
ready | ready |
authenticated | Emitted by SDK once it adopts an established session or restores one. |
change | address_changed, address_selected, payment_method_selected |
order_type_changed | order_type_changed |
request_submit | request_submit |
error | error or SDK auth startup/callback errors |
Checkout lifecycle
Every Cimplify checkout (embedded iframe, hosted Pay session, React `<CimplifyCheckout>`, or headless API flow) passes through the same ordered set of states. Whether you observe them via `onStatusChange`, the `checkout_status` postMessage event, or the `on_status_change` callback on `processCheckout()`, the names and order are identical.
Catalogue
Browse products, variants, categories, collections, bundles, composites, deals, and price quotes. Read-only on the public client; safe to call from any environment.