How do I use hosted checkout?
Create a checkout session, redirect the customer to lomi., then confirm the result with redirects, dashboard records, and webhooks.
Hosted checkout is the recommended first integration for most teams. Your server creates a session, your customer pays on a lomi.-hosted page, and your system confirms the result.
Good to know: Sessions expire after 60 minutes by default. Test keys run in sandbox mode. Cross-cutting rules (coupons, line items, payment state) live in Checkout behavior.
When to use hosted checkout
Use hosted checkout when you want:
- A complete payment page with multiple payment methods.
- Less payment UI and compliance work in your own app.
- A clear success and cancel redirect flow.
- Support for product-based or amount-based checkout.
- A reliable path from sandbox testing to live payments.
Create a checkout session
Your server calls POST /checkout-sessions and redirects the customer to checkout_url.
API reference (full request schema): Create checkout session
Essential fields:
| Field | Notes |
|---|---|
currency_code | Required, XOF, USD, or EUR |
amount | Required unless you pass product_id or line_items |
product_id / price_id | Catalog-backed checkout; amount derived from the price |
success_url / cancel_url | Where to send the customer after pay or cancel |
customer_email | Optional, pre-fills the hosted form |
require_name / require_email / require_phone / require_billing_address | Optional, control checkout form fields; see Checkout form fields |
Pass metadata for your own order IDs. See the API page for every optional field.
Pay what you want products
When product_id or price_id points to a price with pricing_model: pay_what_you_want:
- Omit
amount: the session uses the suggested unit price (amounton the price row) ×quantity. - Pass
amount: must be a valid subtotal: unit price within[minimum_amount, maximum_amount]×quantity. The server rejects out-of-range values. - Hosted checkout: the buyer can change the unit price before paying; validation runs client-side and server-side.
- Programmatic flows: set
amountat session creation when you already know the chosen total.
Multi-item checkout (line_items)
When you pass line_items, each row must reference a one-time price with standard (fixed) pricing. The API returns 400 with one of:
| Code | Meaning |
|---|---|
line_items_pwyw_not_supported | A line uses pay_what_you_want pricing, use a single-product session instead |
line_items_recurring_not_supported | A line references a recurring product |
line_items_usage_based_not_supported | Usage-based pricing (not yet supported in carts) |
line_items_mixed_product_types | Mixed one-time and recurring products in one cart |
Recurring (subscription) products
When product_id refers to a recurring product:
- Omit
amount: the session uses the catalog price for the selectedprice_id(quantity is not multiplied for subscriptions). - Pass
amount: must match the catalog recurring price for validation (unless using prorated first charge at payment time; see Checkout behavior: Subscription checkout). - Trial or
non_initialsignup: the customer may pay $0 at checkout, but the session record often still stores the catalog price socreate_checkout_sessionsatisfiesamount > 0. Do not passamount: 0unless you omit it and rely on product-derived pricing. - Completing checkout creates a subscription (
SUBSCRIPTION_CREATEDwebhook), not a one-timepaymenttransaction only.
Example:
const subscriptionSession = await lomi.checkoutSessions.create({
product_id: 'prod_recurring_abc...',
price_id: 'price_monthly_xyz...',
currency_code: 'XOF',
customer_email: 'customer@example.com',
success_url: 'https://your-site.com/welcome',
allow_coupon_code: true,
});import { LomiSDK } from '@lomi./sdk';
const lomi = new LomiSDK({
apiKey: process.env.LOMI_SECRET_KEY!,
environment: 'live',
});
// Simple checkout with amount
const session = await lomi.checkoutSessions.create({
amount: 10000,
currency_code: 'XOF',
title: 'Order #12345',
description: 'Payment for items in cart',
customer_email: 'customer@example.com',
success_url: 'https://your-site.com/success',
cancel_url: 'https://your-site.com/cancel',
metadata: {
order_id: 'ORD-12345',
},
});
// Product-based checkout
const productSession = await lomi.checkoutSessions.create({
product_id: 'prod_abc123...',
currency_code: 'XOF',
quantity: 2,
allow_coupon_code: true,
success_url: 'https://your-site.com/success',
});
// PWYW product with custom amount and quantity
const pwywSession = await lomi.checkoutSessions.create({
product_id: 'prod_tip_jar...',
currency_code: 'XOF',
amount: 1500,
quantity: 2,
success_url: 'https://your-site.com/success',
});
console.log(`Redirect to: ${session.checkout_url}`);from lomi import LomiClient
import os
client = LomiClient(
api_key=os.environ["LOMI_SECRET_KEY"],
environment="test"
)
# Simple checkout with amount
session = client.checkout_sessions.create({
"amount": 10000,
"currency_code": "XOF",
"title": "Order #12345",
"description": "Payment for items in cart",
"customer_email": "customer@example.com",
"success_url": "https://your-site.com/success",
"cancel_url": "https://your-site.com/cancel",
"metadata": {
"order_id": "ORD-12345"
}
})
# PWYW product with custom amount and quantity
pwyw_session = client.checkout_sessions.create({
"product_id": "prod_tip_jar...",
"currency_code": "XOF",
"amount": 1500,
"quantity": 2,
"success_url": "https://your-site.com/success"
})
print(f"Redirect to: {session['checkout_url']}")curl -X POST "https://api.lomi.africa/checkout-sessions" \
-H "X-API-KEY: $LOMI_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 10000,
"currency_code": "XOF",
"title": "Order #12345",
"description": "Payment for items in cart",
"customer_email": "customer@example.com",
"success_url": "https://your-site.com/success",
"cancel_url": "https://your-site.com/cancel",
"metadata": {
"order_id": "ORD-12345"
}
}'Response
{
"id": "cs_abc123...",
"checkout_url": "https://checkout.lomi.africa/cs_abc123...",
"status": "open",
"amount": 10000,
"currency_code": "XOF",
"title": "Order #12345",
"customer_email": "customer@example.com",
"expires_at": "2024-01-15T11:30:00Z",
"created_at": "2024-01-15T10:30:00Z"
}Embed instead of redirect
To keep the customer on your page, use the embed SDK with the same checkout_url:
import { loadLomiCheckout } from '@lomi./embed';
loadLomiCheckout({
checkoutUrl: session.checkout_url,
mode: 'modal',
onComplete: (payload) => console.log(payload.transactionId),
});See Embed checkout widget for modal, inline, and self-hosted setup.
Confirm the payment
Use redirects for customer experience, not final reconciliation. Your server should rely on webhooks or a server-side API read before fulfilling an order.
Confirm:
- The checkout session status.
- The related transaction status.
- The amount, currency, customer, and metadata.
- The webhook event signature and event ID.
Common variants
| Variant | Use when |
|---|---|
| Fixed amount | Your backend already knows the amount |
| Product checkout | The amount should come from a product or price |
| Subscription checkout | The customer is starting a recurring plan |
| Coupon-enabled checkout | You allow discount codes |
| Test checkout | You are validating with lomi_sk_test_... |
List and retrieve sessions
Use the API reference to list or fetch sessions by ID:
Webhooks
Merchant webhook subscriptions (lomi. calls your URL) use the webhook_event enum documented in Webhooks. The HTTP body field `event` and the `X-Lomi-Event` header use those values (for example `PAYMENT_SUCCEEDED`).
For checkout outcomes on your server, subscribe at minimum to:
authorized_events value | When it fires |
|---|---|
`PAYMENT_SUCCEEDED` | Payment completed; payload `data` follows the transaction shape (see Webhooks). |
`PAYMENT_FAILED` | Payment attempt failed (including some provider failure paths). |
There is no separate `checkout.session.*` event in the `webhook_event` enum. Session expiry without a successful charge is reflected on the session and transaction records (e.g. status `expired`); use `GET /checkout-sessions/{id}` or list with `status=expired` / `completed` rather than expecting a merchant webhook named like checkout.session.expired.