cimplify
Checkout integration

Embedded checkout iframe

Mount the same checkout UI as Cimplify Pay on your own domain. Raw iframe embedding is possible, but saved-details sign-in requires an OAuth bridge; most storefronts should use the SDK controller.

The embedded iframe is the lowest-level checkout integration. It is useful for Vue, Svelte, plain HTML, webviews, or custom platforms. If you are building a React storefront, prefer CimplifyCheckout.

Guest checkout can be wired with plain postMessage. Cimplify saved details require handling the checkout auth request and returning a token with set_token. The SDK already does that bridge.

Iframe URL

https://link.cimplify.io/elements/checkout?businessId=biz_…&nonce=<random>

/elements/payment is an alias of /elements/checkout.

URL Params

ParamRequiredWhat it does
businessIdyesThe Cimplify business this checkout belongs to.
noncerecommendedPer-iframe random string. The iframe echoes it on every message.
emailnoPre-fill the checkout contact field.

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, nonce }. Reply with init.

const iframe = document.getElementById("cimplify-checkout") as HTMLIFrameElement;
const LINK_ORIGIN = "https://link.cimplify.io";
const nonce = "ab12cd34";

window.addEventListener("message", (event) => {
  if (event.origin !== LINK_ORIGIN) return;
  if (event.source !== iframe.contentWindow) return;
  if (event.data?.nonce !== nonce) return;

  if (event.data?.type === "ready") {
    iframe.contentWindow!.postMessage({
      type: "init",
      businessId: "biz_01J5…",
      publicKey: "cpk_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

window.addEventListener("message", (event) => {
  if (event.origin !== LINK_ORIGIN) return;
  if (event.source !== iframe.contentWindow) return;
  if (event.data?.nonce !== nonce) 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.

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);

Handling Saved-Details Sign-In

When the shopper enters a valid contact and chooses Cimplify, checkout emits:

{
  type: "auth_requested",
  loginHint: "jane@example.com",
  contactType: "email",
  mode: "checkout",
  nonce: "ab12cd34"
}

A raw iframe parent must do one of these:

  1. Use @cimplify/sdk just for the checkout controller or startSignIn, which handles OAuth, PKCE, web_message, callback exchange, and set_token.
  2. Implement the OAuth bridge yourself.

The successful end of the flow is always:

iframe.contentWindow!.postMessage({
  type: "set_token",
  token: accessTokenFromYourCallback,
}, LINK_ORIGIN);

For a delightful checkout, do not show a separate "are you a member?" step and do not reveal account existence. Let checkout collect the contact, then start OAuth with that contact as login_hint. Cimplify will silently use an existing matching SSO session, or show OTP for that contact.

Starting Checkout

With renderSubmitButton: true, the iframe renders its own Pay button and emits request_submit. Reply with process_checkout.

window.addEventListener("message", (event) => {
  if (event.origin !== LINK_ORIGIN) return;
  if (event.source !== iframe.contentWindow) return;
  if (event.data?.nonce !== nonce) 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.
  • Always check event.source.
  • Use a random per-iframe nonce and verify it on incoming messages.
  • Send messages to "https://link.cimplify.io", not "*".
  • Include allow-same-origin and allow-popups in the sandbox. Link sessions and some payment providers need them.
  • Post only the short-lived access token returned by your callback.

When to Graduate

Once you are writing more than a small message handler, switch to CimplifyCheckout or Vanilla checkout. The SDK controller handles auth, origin checks, nonce routing, token broadcast, checkout processing, aborts, and timeouts.

Next

On this page