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.
Flow
1. Customer adds items to cart on your storefront.
2. Your server hits POST /v1/checkout/sessions with the cart_id and a secret key.
3. The API returns { id, url, status, expires_at }.
4. You 302 the customer to `url` (or open it in a popup).
5. Customer completes payment on pay.cimplify.io.
6. Cimplify redirects them to your success_url with ?order_id=... &session_id=...
AND fires order.completed / payment.succeeded webhooks.Create a session
Authenticate with a secret key (sk_…). The session is bound to whatever business that key belongs to.
curl https://api.cimplify.io/v1/checkout/sessions \
-H "Authorization: Bearer $CIMPLIFY_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"cart_id": "crt_01J5...",
"public_key": "pk_live_…",
"success_url": "https://store.example.com/orders/thanks",
"cancel_url": "https://store.example.com/cart",
"default_order_type": "delivery",
"submit_label": "Pay GH₵29.99"
}'Request body
| Field | Type | Required | Notes |
|---|---|---|---|
cart_id | string | yes | An existing cart belonging to the same business as the API key. |
public_key | string | no | The pk_… key to embed in the hosted page; defaults to the business's primary public key. |
order_types | string[] | no | Subset of delivery | pickup | dine_in. Defaults to whatever the business supports. |
default_order_type | string | no | Pre-select an order type. |
currency | string | no | ISO 4217. Defaults to the cart currency. |
success_url | string | no | Cimplify redirects here on success with order_id and session_id query params. |
cancel_url | string | no | Shown as a "Return to store" button on the hosted page. |
appearance | object | no | ElementAppearance. |
submit_label | string | no | Override the Pay button copy. |
metadata | object | no | Free-form JSON echoed on the resulting order. |
Response
{
"id": "cs_01J5BGM...",
"url": "https://pay.cimplify.io/s/cs_01J5BGM...",
"status": "open",
"expires_at": "2026-05-07T17:00:00Z"
}Redirect the customer
// app/api/checkout/route.ts
import { redirect } from "next/navigation";
export async function POST(request: Request) {
const { cartId } = await request.json();
const r = 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: cartId,
success_url: `${process.env.STORE_URL}/orders/thanks`,
cancel_url: `${process.env.STORE_URL}/cart`,
}),
});
if (!r.ok) {
return Response.json({ error: await r.text() }, { status: 500 });
}
const { url } = await r.json() as { url: string };
redirect(url);
}Reading the success redirect
Cimplify appends order_id and session_id to your success_url. Treat the redirect as a UI hint only; the source of truth is the webhook. Don't fulfill orders from the redirect alone.
export default async function ThanksPage({
searchParams,
}: {
searchParams: Promise<{ order_id?: string; session_id?: string }>;
}) {
const { order_id } = await searchParams;
if (!order_id) return <p>Awaiting confirmation…</p>;
const r = await getServerClient().orders.get(order_id);
if (!r.ok) return <p>Order not found.</p>;
return <h1>Thanks! Order #{r.value.order_number}</h1>;
}Webhooks
Wire the order.completed and payment.succeeded events to your fulfillment system. See webhooks for signing and replay.
Session status
| Status | Meaning |
|---|---|
open | Customer has not completed yet. URL is usable. |
completed | Payment captured; an order exists. |
expired | Past expires_at. Issue a new session. |
Next
- Pay sessions reference: Full API surface for the hosted product
- Embedded iframe: Keep the customer on your domain
Server Components
`@cimplify/sdk/server` is a Node-only entry for the Next.js App Router. It ships three things: a request-memoized client factory, a typed cache-tag scheme, and Server Action revalidation helpers.
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).