> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lite.sa/llms.txt
> Use this file to discover all available pages before exploring further.

# Hosted Page

> Redirect customers to a Lite-hosted checkout page and confirm payment with webhooks.

Redirect customers to a complete checkout page that Lite builds, serves, and processes. When a customer is ready to pay, you send them from your site to a Lite URL. They enter their card there, lite processes the payment on its own servers, and they land back on your site with the result.

Because the card details live entirely on lite's infrastructure, they never touch your servers. You never store, transmit, or handle a raw card number, which can make you eligible for the simplest level of PCI validation; SAQ A.

**Use a Hosted Page if you:**

* aren't PCI-DSS certified and want to keep it that way,
* want the fastest integration with the least code, or
* are happy to send customers to a lite-hosted (but brand-able) page to pay.

## How it works

1. **Create a session.** Your server calls `POST /v1/checkout/sessions` and gets back a `payment_link`.
2. **Redirect the customer.** You send them to the `payment_link`. They pay on lite's hosted page.
3. **Customer returns.** lite sends cardholders back to your `success` or `failure` URL, so you can show a result right away.
4. **Confirm with a webhook.** lite sends a `payment.captured` event to your server. This is the source of truth you fulfill from.

```mermaid theme={null}
sequenceDiagram
    participant C as Customer
    participant M as Your server
    participant L as Lite
    C->>M: Starts checkout
    M->>L: POST /v1/checkout/sessions
    L-->>M: session.payment_link
    M-->>C: 303 redirect to payment_link
    C->>L: Enters card and pays on Lite's page
    L-->>C: Redirect to success / failure URL
    L->>M: Webhook payment.captured
    M-->>L: 200 OK, then fulfill the order
```

A checkout session is a short-lived attempt to collect payment. It resolves to `Completed`, `Expired`, or `Cancelled`. The return URL tells you the session resolved; the webhook tells you the money actually moved. Treat the webhook as your source of truth — see [Step 4](#step-4-confirm-with-a-webhook).

## Before you begin

This guide assumes your one-time setup is done. You can re-check any of it from the dashboard. You'll need:

* A lite account in good standing.
* Test and live API keys (`sk_test_…` / `sk_live_…`).
* At least one channel configured, with its `channel_id` (unless you intend to use the default channel option). A channel sets your enabled payment methods, branding, and default redirect URLs.
* A webhook endpoint registered, you'll need it for fulfillment in Step 4.

Build and test everything with your test key first.

**Going live takes more than a live key.** To process real payments, your account needs completed KYB (Know Your Business) verification and active payment acceptance. You can track both from the dashboard. Until they're done, you will only see lite sandbox.

## Step 1: Create a session

Create the session from your backend, so a customer can't tamper with the amount or currency.

```bash theme={null}
curl https://{{url}}/v1/checkout/sessions \
  -H "x-api-key: $LITE_API_KEY" \
  -H "x-correlation-id: $(uuidgen)" \
  -H "x-idempotency-key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "channel_id": "550e8400-e29b-41d4-a716-446655440000",
    "amount": 4999,
    "currency": "SAR",
    "customer": {
      "email": "john.doe@email.com",
      "first_name": "John",
      "last_name": "Doe"
    },
    "order": { "reference": "ORD-123456" },
    "redirect_urls": {
      "success": "https://example.com/return?session_id={SESSION_ID}",
      "failure": "https://example.com/checkout"
    },
    "expiry": 1800
  }'
```

### Required headers

| Header              | Required    | Purpose                                                                                                            |
| ------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ |
| `x-api-key`         | Yes         | Authenticates the request. Use your test key in test mode. Or send `Authorization: Bearer <JWT>` instead.          |
| `x-idempotency-key` | Recommended | A UUID per logical session. Retries with the same key return the original session instead of creating a duplicate. |
| `x-correlation-id`  | Optional    | A UUID per request, echoed in logs. Quote it in support tickets.                                                   |

### Key request fields

| Field               | Required | Notes                                                                                                                           |
| ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `customer`          | Yes      | Customer details. The individual fields (email, name, phone) are each optional.                                                 |
| `amount`            | Yes      | In minor units (`4999` = 49.99 SAR). The exponent varies by currency: SAR has 2 decimals, KWD has 3, JPY has 0.                 |
| `currency`          | Yes      | ISO 4217 code.                                                                                                                  |
| `redirect_urls`     | Yes      | The `success` and `failure` URIs the customer returns to.                                                                       |
| `order`             | Yes      | Order details as price, quantity, shipping and vat. `order.reference` (your order ID) is required and comes back as `order_id`. |
| `channel_id`        | No       | Applies a channel's branding, enabled payment methods, and redirect URLs.                                                       |
| `expiry`            | No       | Session lifetime in seconds (60 to 86400). Defaults to 3600.                                                                    |
| `three_ds_required` | No       | Force 3D Secure on. Otherwise it's applied automatically per scheme and SAMA rules.                                             |

### Response

```json theme={null}
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "payment_link": "https://checkout.lite.sa/ses_a1b2c3d4e5f6",
  "client_secret": "cs_a1b2c3d4e5f6",
  "status": "Pending",
  "expires_on": "2026-06-01T12:30:00.000Z",
  "order_id": "ORD-123456",
  "amount": 4999,
  "currency": "SAR"
}
```

A new session always starts as `Pending`. Hold on to the `id` (a UUID), you'll use it to match the webhook, and to look the session up later if you need to. (`client_secret` is only for the Drop-in SDK; you can ignore it for the hosted redirect.)

By default, 3D Secure is applied automatically based on the card scheme and SAMA rules; for mada and most KSA cards it's mandatory. Set `three_ds_required: true` if you want to force it on every payment.

## Step 2: Redirect to the hosted page

Send the customer to `payment_link` with an HTTP 303:

```js theme={null}
res.redirect(303, session.payment_link);
```

Use `303`, not `302`, so the browser switches to a `GET` for the checkout page even though your endpoint received a `POST`. From here the customer pays entirely on Lite's domain — you never see their card details.

## Step 3: Handle the return

After checkout, lite sends the customer back to one of your `redirect_urls`:

* `success`: the payment went through.
* `failure`: the payment failed, or the customer cancelled.

Use this page to show them something right away — an order confirmation or a retry prompt. Don't fulfill the order here.

> \[!WARNING]
> The return URL is not authoritative. A customer can pay and then close the tab or drop offline before your page loads, so the redirect might never reach you. Always fulfill from the webhook in Step 4.

If you want to show a definitive state on this page, check the session server-side first; but still fulfill from the webhook. Retrieve the full session, or call the lightweight validity endpoint:

```js theme={null}
const session = await lite.checkout.sessions.retrieve(req.query.session_id);
// or GET /checkout/sessions/{session_id}/validity for a quick { valid, status } check
// render based on session.status, but still fulfill from the webhook
```

## Step 4: Confirm with a webhook

Webhooks are how you reliably learn the outcome of every payment. For the Hosted Page flow, subscribe to these events:

| Event                          | When it fires                                            |
| ------------------------------ | -------------------------------------------------------- |
| `payment.captured`             | Payment succeeded and funds were captured. Fulfill here. |
| `payment.authorization_failed` | The payment was declined.                                |
| `payment.refunded`             | A refund completed.                                      |

## Test and go live

Test mode accepts lite's published test cards. At a minimum, confirm each of these outcomes:

| Scenario                             | Expected result                                                     |
| ------------------------------------ | ------------------------------------------------------------------- |
| Successful mada payment              | Session `Completed`, `payment.captured` webhook fires               |
| Successful Visa / Mastercard payment | Session `Completed`, `payment.captured` webhook fires               |
| Declined card                        | Session not completed, `payment.authorization_failed` webhook fires |

**Before you switch to your live key, confirm that:**

* Your account has KYB verification complete and payment acceptance activated.
* Your webhook endpoint verifies the signature and returns `200`.
* A session creates successfully and returns a `payment_link`.
* The redirect reaches lite's hosted page and back to your return URL.
* Fulfillment fires from `payment.captured`, not from the return URL.
* A declined card shows up correctly on your `failure` page.

For full event payloads, the signature scheme, and the retry schedule, see the webhooks reference.
