cimplify
Checkout integration

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

ParamRequiredWhat it does
businessIdyesThe Cimplify business this checkout belongs to.
noncerecommended16-char per-iframe random string. The element echoes it on every message; the parent uses it to route messages when multiple Elements coexist.
emailnoPre-fill the contact field.
modenoOnly 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 of postMessage, never "*".
  • The iframe's sandbox attribute must include allow-same-origin (the iframe needs cookies for Link sessions) and allow-popups (some payment providers redirect via popup).
  • Don't set the nonce from a predictable value; use crypto.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

On this page