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

# Mint a Checkout Session Token

> Issue a short-lived, single-purpose token bound to one payment request and use it to drive the embedded checkout widget without exposing your merchant apiKey.

# Overview

A **checkout session token** is a short-lived secret that lets the embedded checkout iframe authenticate against the Caibo payment API without ever seeing your merchant `apiKey`. Your backend mints the token using your `apiKey`, hands it to the browser, and the SDK passes it to the checkout page in the `paymentUrl`.

<Warning>
  Never embed your merchant `apiKey` in the iframe URL, in client-side code, in browser history, or in any URL that leaves your server. The `apiKey` is a long-lived bearer secret with full merchant access. Always exchange it for a checkout session token on the server.
</Warning>

## Security model

| Property               | Merchant `apiKey`                     | Checkout session token                                          |
| ---------------------- | ------------------------------------- | --------------------------------------------------------------- |
| Lifetime               | Long-lived (until rotated)            | **15 minutes** from issuance                                    |
| Scope                  | Full merchant API access              | Read **one** payment request + submit its payment               |
| Bound to               | The merchant                          | A single `paymentRequestId`                                     |
| Where it lives         | Merchant backend only                 | Browser session (sessionStorage) and a single URL/header        |
| Where to transmit it   | `X-API-Key` header — server-to-server | `X-Checkout-Token` header, or `?token=` in the iframe `src` URL |
| Mintable from a token? | n/a                                   | **No** — tokens cannot mint more tokens                         |

A leaked checkout token can only act on the bound payment request, and only for the next 15 minutes. A leaked `apiKey` can drain the account.

## Endpoint

```http theme={null}
POST {api_base}/payment-requests/{paymentRequestId}/checkout-session
Header: X-API-Key: <your-merchant-api-key>
Content-Type: application/json
```

| Parameter          | In   | Type   | Description                                                                                 |
| ------------------ | ---- | ------ | ------------------------------------------------------------------------------------------- |
| `paymentRequestId` | path | number | The id returned by [Create a Payment Request](/ipg/iframe-checkout/create-payment-request). |

The request body is empty (`{}`). The token derives its scope entirely from the path id and the authenticated merchant.

<Info>
  This call MUST originate from your backend. The browser must never see your `apiKey`. The whole point of this endpoint is to keep the long-lived secret on the server.
</Info>

## Response

```json theme={null}
{
  "token": "bhMlOWTW16rHxnmOugI9Uu1qNJk4IzD0-EZfGsRLJDU",
  "paymentRequestId": 17784899067150744,
  "expiresAt": "2026-05-11T13:25:33Z",
  "ttlSeconds": 900
}
```

| Field              | Type   | Description                                                            |
| ------------------ | ------ | ---------------------------------------------------------------------- |
| `token`            | string | URL-safe base64 secret. 32 random bytes ≈ 43 characters.               |
| `paymentRequestId` | number | The payment request the token is bound to.                             |
| `expiresAt`        | string | ISO-8601 UTC timestamp when the token stops being accepted.            |
| `ttlSeconds`       | number | Total lifetime of the token in seconds (currently `900` = 15 minutes). |

## Using the token

### As a URL parameter (iframe `src`)

The iframe must boot from a URL — it cannot set a custom header on its initial document load — so the SDK accepts the token as a `?token=` query parameter. The Caibo checkout page consumes the value on bootstrap, stores it in `sessionStorage`, then sends it on every subsequent request as `X-Checkout-Token`.

```text theme={null}
https://pay.caibo.digital/main?requestId=REQUEST_ID&token=TOKEN
```

Notice there is **no `apiKey`** and **no `merchantId`** in the URL. The token already binds the merchant and the request server-side.

### As a header (server-to-server calls from a non-browser client)

If your integration calls a permitted endpoint outside the iframe SDK, send the token as a header instead:

```http theme={null}
X-Checkout-Token: bhMlOWTW16rHxnmOugI9Uu1qNJk4IzD0-EZfGsRLJDU
```

## What a token is allowed to do

A request bearing a checkout session token is authorised for a small, fixed allow-list of endpoints, and every endpoint that carries a `paymentRequestId` (in the path or query string) must match the bound id exactly. Anything else returns `403 Forbidden`.

| Method | Path                                                 | Notes                                                                         |
| ------ | ---------------------------------------------------- | ----------------------------------------------------------------------------- |
| `GET`  | `/payment-requests/{id}`                             | Must equal the bound id.                                                      |
| `GET`  | `/users/settings/{merchantId}`                       | Branding / theme.                                                             |
| `GET`  | `/users/payment-methods/{merchantId}?requestId={id}` | `requestId` must equal the bound id.                                          |
| `GET`  | `/users/logos/{merchantId}`                          | Merchant logo.                                                                |
| `GET`  | `/users/addresses/{merchantId}`                      | Merchant address (used by Apple Pay domain check).                            |
| `GET`  | `/payments/wallet-config`                            | Apple / Google Pay configuration.                                             |
| `POST` | `/payments/creditCard/{id}`                          | Must equal the bound id. Same for `creditCardToken`, `googlePay`, `applePay`. |
| `GET`  | `/payments/creditCard/status/{transactionId}`        | Polling.                                                                      |
| `GET`  | `/payments/threeds/status/{transactionId}`           | 3DS polling.                                                                  |
| `GET`  | `/payments/threeds/redirect/{transactionId}`         | 3DS redirect.                                                                 |
| `POST` | `/payments/threeds/ddc/{transactionId}`              | 3DS device data collection.                                                   |

A checkout token explicitly **cannot** call `POST /payment-requests/{id}/checkout-session` — tokens cannot mint additional tokens. Use your `apiKey` to re-mint when one expires.

## Lifecycle

<Steps>
  <Step title="Create the payment request (server, apiKey)">
    `POST /payment-requests` with `X-API-Key`. Save the returned `id`.
  </Step>

  <Step title="Mint the token (server, apiKey)">
    `POST /payment-requests/{id}/checkout-session` with `X-API-Key`. Save `token` and `expiresAt`.
  </Step>

  <Step title="Deliver the token to the browser">
    Return the `token` (and `requestId`) to your frontend via your own authenticated endpoint. The browser must not see the `apiKey`.
  </Step>

  <Step title="Open the iframe (browser, token)">
    Construct `paymentUrl = .../main?requestId=…&token=…` and call `CaiboCheckout.init({ paymentUrl, ... })`.
  </Step>

  <Step title="Token expires after 15 minutes">
    If the customer is still on the page when the token expires, mint a fresh one server-side and re-open the iframe. The token is not auto-refreshed.
  </Step>
</Steps>

## Error responses

| Status | Cause                                                                   |
| ------ | ----------------------------------------------------------------------- |
| `401`  | `apiKey` missing or invalid on the mint call.                           |
| `403`  | Caller authenticated with a checkout token (cannot mint another token). |
| `404`  | No payment request with the given `paymentRequestId`.                   |

Once issued, errors when **using** a token surface as:

| Status | Cause                                                                                       |
| ------ | ------------------------------------------------------------------------------------------- |
| `401`  | Token unknown, expired, revoked, or exhausted.                                              |
| `403`  | Endpoint is not on the token's allow-list, or the path/query `paymentRequestId` mismatches. |

## Backward compatibility

Legacy integrations that still pass `?apiKey=` in the `paymentUrl` continue to work, but the checkout page emits a `console.warn` deprecation notice on every load. Plan a migration to the token flow — leaking an `apiKey` through the browser history or `Referer` header is a serious incident.

## Next Steps

* [Quick Start — Inline & Modal](/ipg/iframe-checkout/quick-start) — paste-ready mint + init snippets.
* [Framework Examples](/ipg/iframe-checkout/framework-examples) — full backend-mints + frontend-uses pattern in React, Angular, Vue.
* [SDK API Reference](/ipg/iframe-checkout/sdk-api-reference) — every option, event, and payload.
