Embedded checkout iframe
The same checkout UI as the hosted Pay page, but you mount it as an iframe on your own domain. No SDK needed; just an `<iframe>` and a few postMessage listeners. Use this when you want to stay on your domain but don't need React (or want to mount inside Vue, Svelte, plain HTML, a webview, etc).
The iframe URL
https://link.cimplify.io/elements/checkout?businessId=biz_…&nonce=<random>/elements/payment is an alias of /elements/checkout. Both render the same React component (CheckoutElement from packages/link/src/pages/elements/checkout/). Pick whichever you prefer.
URL params
| Param | Required | What it does |
|---|---|---|
businessId | yes | The Cimplify business this checkout belongs to. |
nonce | recommended | 16-char per-iframe random string. The element echoes it on every message; the parent uses it to route messages when multiple Elements coexist. |
email | no | Pre-fill the contact field. |
mode | no | Only on the address element: shipping or billing. |
Minimal embed
<iframe
id="cimplify-checkout"
src="https://link.cimplify.io/elements/checkout?businessId=biz_01J5…&nonce=ab12cd34"
style="border:0; width:100%; min-height:200px; display:block; background:transparent"
allowtransparency="true"
allow="geolocation"
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
></iframe>Init handshake
The iframe posts { type: "ready", height } as soon as it's interactive. Reply with an init message containing the public key, appearance, and order options.
const iframe = document.getElementById("cimplify-checkout") as HTMLIFrameElement;
const LINK_ORIGIN = "https://link.cimplify.io";
window.addEventListener("message", (event) => {
if (event.origin !== LINK_ORIGIN) return;
if (event.data?.type !== "ready") return;
iframe.contentWindow!.postMessage({
type: "init",
businessId: "biz_01J5…",
publicKey: "pk_live_…",
appearance: {
theme: "light",
variables: { primaryColor: "#059669", borderRadius: "0.85rem" },
},
orderTypes: ["delivery", "pickup"],
defaultOrderType: "delivery",
renderSubmitButton: true,
submitLabel: "Pay GH₵29.99",
}, LINK_ORIGIN);
iframe.style.height = event.data.height + "px";
});Auto-resize
The iframe emits height_change whenever its content resizes. Apply it verbatim.
window.addEventListener("message", (event) => {
if (event.origin !== LINK_ORIGIN) return;
if (event.data?.type === "height_change") {
iframe.style.height = event.data.height + "px";
}
});Pushing the cart
Send set_cart to render an order summary alongside the form (the embedded UI auto-switches to a two-column layout when a cart is present).
iframe.contentWindow!.postMessage({
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",
},
}, LINK_ORIGIN);Starting checkout
With renderSubmitButton: true the iframe renders its own Pay button and emits request_submit when pressed. Reply with process_checkout.
window.addEventListener("message", (event) => {
if (event.origin !== LINK_ORIGIN) return;
if (event.data?.type === "request_submit") {
iframe.contentWindow!.postMessage({
type: "process_checkout",
cart_id: cart.id,
order_type: "delivery",
enroll_in_link: true,
}, LINK_ORIGIN);
}
if (event.data?.type === "checkout_complete") {
if (event.data.success) {
window.location.href = `/orders/${event.data.order.id}`;
} else {
console.error(event.data.error);
}
}
});Security
- Always check
event.origin === "https://link.cimplify.io"before trusting a message. - Always send messages to
"https://link.cimplify.io"as the second arg ofpostMessage, never"*". - The iframe's
sandboxattribute must includeallow-same-origin(the iframe needs cookies for Link sessions) andallow-popups(some payment providers redirect via popup). - Don't set the
noncefrom a predictable value; usecrypto.randomUUID().
When to graduate
Once you're writing more than ~30 lines of postMessage glue, switch to Controlled Elements (React) or Vanilla Elements (any framework); the SDK's CimplifyElements controller handles all of the above for you.
Next
- Element events: Full message catalog
- Vanilla Elements: SDK-managed mount, no React
Drop-in checkout (hosted Pay)
The fastest path to a paid order: create a checkout session on your server, then redirect the customer to the URL it returns. Cimplify hosts the entire checkout UI at `pay.cimplify.io/s/<sessionId>` (auth, address, payment method, compliance). You get a webhook (or success-URL redirect) when payment lands.
Controlled Elements (React)
React wrappers around the Cimplify Element iframes. Drop a component into your tree, pass a few props, and you have a working checkout. The iframe still does all the rendering; you just orchestrate it with React.