Choosing a checkout integration
Cimplify ships five layered ways to take a payment. Pick the highest tier you can; each step down the list trades a few minutes of integration time for more control. All five eventually reach the same backend endpoint and emit the same [Element events](/docs/concepts/element-events).
The five tiers
| Tier | Hosted by | Roughly | You give up |
|---|---|---|---|
| Drop-in (hosted Pay) | pay.cimplify.io | 5 LOC + a redirect | Customer leaves your site |
| Embedded iframe | link.cimplify.io | ~20 LOC | You wire postMessage yourself |
| Controlled Elements (React) | Iframe inside your React tree | 1 component, ~30 LOC | You stay on the React side of the iframe |
| Vanilla Elements | Iframe inside your DOM | ~50 LOC | You manage mount/unmount manually |
Headless (CheckoutPage or your own) | You | Open-ended | No iframe; you build every screen |
Decision tree
Do you ship a frontend at all?
No → Drop-in (Pay session). Done.
Yes → Do you need to keep the customer on your domain?
No → Drop-in.
Yes → Are you on React?
No → Vanilla Elements.
Yes → Do you want Cimplify-rendered UI for auth/address/payment?
Yes → Controlled Elements (`<CimplifyCheckout>` or piecewise).
No → Headless (`<CheckoutPage>` or your own UI driving `client.checkout.process`).What stays the same across all tiers
- The checkout lifecycle (
preparing → processing → … → success | failed) is identical. - The cart shape (built via
client.cart.addItem({ item_id, quantity, ... })) is the same. Drop-in / Pay sessions accept acart_id; Elements expect the cart to already exist on the customer's session cookie. - Server-side, every tier funnels into
POST /v1/checkoutvia the sameCheckoutFormDatabody. - Appearance API works the same way for every iframe-based tier.
What differs
| Concern | Drop-in | Embedded / Elements / Vanilla | Headless |
|---|---|---|---|
| Where the iframe lives | pay.cimplify.io | Your page | |
| Cimplify-rendered fields | All of them | All of them | None |
| Compliance scope (PCI, OTP) | Cimplify | Cimplify (data never leaves the iframe) | You |
| Cart pre-fill UX | Server-built session | Use set_cart / cartId prop | You |
| Custom layout/copy | Limited (Appearance API) | Limited (Appearance API) | Unlimited |
Two-line drop-in example
For most teams this is the right starting point. You can always graduate to embedded later.
// Server: create a session with your secret key
const session = await fetch("https://api.cimplify.io/v1/checkout/sessions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.CIMPLIFY_SECRET_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ cart_id: cart.id, success_url, cancel_url }),
}).then(r => r.json());
return Response.redirect(session.url);Next
- Drop-in (hosted Pay): Redirect to
pay.cimplify.io - Controlled Elements: React components that render the iframe