Utopia PaymentsDocs

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" }
  }'
Response
{
  "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.

GET /payments/pay_7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d
{
  "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; null until the payment settles.
  • net_amount: what you keep, total_amountrefunded_amountfee_amount; null until 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.

On this page