One-time payments
Create a hosted checkout, send the customer to it, and fulfil the order from the webhook.
1. Create a checkout session
Send the products the customer is buying. Each item in product_cart is a
product you created, in the dashboard or with POST /products, and a quantity.
All items must share a currency.
Amounts are integer minor units. Read Currencies and amounts before accepting KWD, BHD or OMR.
curl https://utopia-payments.com/api/v1/checkout_sessions \
-H "Authorization: Bearer $UTOPIA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1001" \
-d '{
"product_cart": [{ "product_id": "pdt_3f2b8c1e9a4d4e7b8c2a1d5e6f7a8b9c", "quantity": 1 }],
"customer": { "email": "customer@example.com" },
"return_url": "https://your-store.com/thank-you",
"metadata": { "order_id": "1001" }
}'{
"object": "checkout_session",
"session_id": "cks_9b1e44a0c3d24f6e8a7b5c2d1e0f9a8b",
"kind": "payment",
"status": "open",
"checkout_url": "https://utopia-payments.com/checkout/cks_9b1e44a0c3d24f6e8a7b5c2d1e0f9a8b",
"total_amount": 34900,
"currency": "AED",
"product_cart": [
{ "product_id": "pdt_3f2b8c1e9a4d4e7b8c2a1d5e6f7a8b9c", "name": "Pro course", "quantity": 1, "unit_amount": 34900 }
],
"return_url": "https://your-store.com/thank-you",
"payment_id": null,
"subscription_id": null,
"metadata": { "order_id": "1001" },
"livemode": true,
"expires_at": "2026-09-15T10:00:00.000Z"
}Sessions last 24 hours.
Carts without saved products
Carts that don't map to saved products, such as a store's order total, can price items inline:
await utopia.checkoutSessions.create({
product_cart: [{ name: 'Order #1001', unit_amount: 12550, currency: 'AED', quantity: 2 }],
return_url: 'https://your-store.com/orders/1001',
});2. The customer pays
The hosted checkout collects the customer's details and card in the secure
payment window. Afterwards the customer lands on your return_url with
payment_id, status and session_id appended.
Treat those query parameters as a hint for what to show. Fulfil orders from the
payment.succeeded webhook, or confirm with GET /payments/:id before
shipping anything.
3. Fulfil from the webhook
payment.succeeded carries the payment, including the metadata you sent, so
you can find the order it belongs to. See Webhooks.
{
"object": "payment",
"payment_id": "pay_7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d",
"status": "succeeded",
"total_amount": 34900,
"refunded_amount": 0,
"fee_amount": 3012,
"net_amount": 31888,
"currency": "AED",
"description": "Pro course",
"customer": {
"customer_id": "cus_2c4e6a8b0d1f4a3c5e7b9d1f3a5c7e9b",
"name": "Layla Hassan",
"email": "customer@example.com",
"phone_number": "+971501234567"
},
"checkout_session_id": "cks_9b1e44a0c3d24f6e8a7b5c2d1e0f9a8b",
"subscription_id": null,
"billing_reason": "payment",
"metadata": { "order_id": "1001" },
"livemode": true,
"created_at": "2026-09-14T09:58:12.000Z",
"paid_at": "2026-09-14T10:00:00.000Z"
}Amounts are integers in the currency's minor unit (fils for AED).
fee_amount: Utopia's processing fee, fixed when the payment succeeds and kept after a refund;nulluntil the payment settles.net_amount: what you keep,total_amount−refunded_amount−fee_amount;nulluntil the payment settles, and below zero after a full refund because the fee is kept.
See Payment lifecycle for every checkout and payment status, and Customers for matching by email.
Refunds
There is currently no public Refunds API and no merchant dashboard button that
initiates a refund. Do not build against an undocumented refund endpoint.
Refunds handled operationally are reflected in refunded_amount, payment
status and the refund.succeeded webhook. The processing fee remains in
fee_amount after a refund.