Checkout lifecycle
Every Cimplify checkout (embedded iframe, hosted Pay session, or controlled React Element) 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.
The states
Defined by the CheckoutStatus union exported from @cimplify/sdk:
type CheckoutStatus =
| "preparing"
| "recovering"
| "processing"
| "awaiting_authorization"
| "polling"
| "finalizing"
| "success"
| "failed";What each state means
| Status | When it fires |
|---|---|
preparing | The Element has received process_checkout and is gathering customer/payment context. Brief, usually a few hundred ms. |
recovering | An interrupted checkout was found in storage (page reloaded mid-flow) and is being resumed. Skipped on a fresh checkout. |
processing | The order has been submitted to the API; the payment provider is being charged. |
awaiting_authorization | The provider returned a challenge: customer must enter an OTP, PIN, birthday, phone or address before the payment can complete. context.authorization_type tells you which. |
polling | The payment was authorized and the SDK is polling the order endpoint for the final settled state. context.poll_attempt and context.max_poll_attempts are populated. |
finalizing | Payment is captured; the order record is being written and webhooks dispatched. |
success | Terminal. The order is paid. context.order_id and context.order_number are populated. |
failed | Terminal. Customer can usually retry; error.recoverable on the final checkout_complete event tells you whether to keep the form or reset it. |
Status context
Each transition carries a CheckoutStatusContext with optional fields. Use display_text for a human-readable status line; use the structured fields for progress UI.
interface CheckoutStatusContext {
display_text?: string;
authorization_type?: "otp" | "pin" | "phone" | "birthday" | "address";
poll_attempt?: number;
max_poll_attempts?: number;
order_id?: string;
order_number?: string;
provider?: string;
}Typical flow
mount → ready (parent posts init)
↓
[user fills form, presses Pay]
↓
preparing → processing
↓
┌──────────────┴──────────────┐
│ │
▼ ▼
awaiting_authorization polling
│ │
└──→ processing ──→ polling ─┘
↓
finalizing
↓
success | failed (terminal)Observing transitions
From a parent page (postMessage)
window.addEventListener("message", (event) => {
if (event.data?.type === "checkout_status") {
console.log(event.data.status, event.data.context);
}
});From CimplifyCheckout
<CimplifyCheckout
client={client}
cartId={cart.id}
onStatusChange={(status, ctx) => {
setStatusLabel(ctx.display_text ?? status);
}}
onComplete={(result) => {
if (result.success) router.push(`/orders/${result.order!.id}`);
}}
/>From processCheckout()
const elements = client.elements(businessId);
const result = await elements.processCheckout({
cart_id: cart.id,
order_type: "delivery",
on_status_change: (status, ctx) => {
if (status === "awaiting_authorization") {
// Show OTP/PIN entry tied to ctx.authorization_type
}
},
});Terminal events
After success or failed, the iframe emits a single checkout_complete event with the full result. Stop reacting to checkout_status at that point; the iframe will not send further status updates for this checkout.
{
type: "checkout_complete",
success: true,
order: {
id: "ord_…",
order_number: "#1042",
status: "completed",
total: "29.99",
currency: "GHS"
},
enrolled_in_link: true
}Recovery
If the customer reloads during awaiting_authorization or polling, the Element rehydrates from local storage and resumes. The first status you receive on the new page will be recovering, then back into the original state.
Next
- Element events: The full postMessage protocol
- Controlled Elements: Subscribe to lifecycle from React
Webhooks
Cimplify pushes order, payment, and booking lifecycle events to your endpoint as JSON over HTTPS. Every delivery is HMAC-signed and replayed with exponential backoff if your server doesn't return a 2xx within 30 seconds.
Element events
Cimplify Elements communicate with the parent page via `window.postMessage`. Below is the full event union sent _from_ the iframe, defined as `IframeToParentMessage` in `@cimplify/sdk`. The parent-side controller in the SDK already validates and dispatches these for you; this page is for when you want to wire them up directly.