# Payment sessions (/docs/accept-payments/payment-sessions)



Kashier's payment sessions make transactions more secure and efficient. With a single API call, you can create payments without exposing sensitive data in query strings, and session history tracking lets you monitor every action in real time. This page walks through creating a session, sending the customer to pay, and confirming the result.

## Step 1: Create a payment session [#step-1-create-a-payment-session]

| Endpoint | Value                                                                                              |
| -------- | -------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v3/payment/sessions](https://test-api.kashier.io/v3/payment/sessions) |
| LIVE-URL | [https://api.kashier.io/v3/payment/sessions](https://api.kashier.io/v3/payment/sessions)           |
| Method   | POST                                                                                               |

```bash
curl --location 'https://test-api.kashier.io/v3/payment/sessions' \
  --header 'Authorization: YOUR_TEST_SECRET_KEY' \
  --header 'api-key: YOUR_TEST_API_KEY' \
  --header 'Content-Type: application/json' \
  --data '{
  "expireAt": "2030-01-28T17:27:32.359Z",
  "maxFailureAttempts": 3,
  "paymentType": "credit",
  "amount": "100.00",
  "currency": "EGP",
  "order": "8f64saf6sa4",
  "merchantRedirect": "https://your-website.com/redirect",
  "display": "en",
  "type": "one-time",
  "allowedMethods": "card,wallet",
  "redirectMethod": null,
  "iframeBackgroundColor": "#FFFFFF",
  "metaData": {
      "customKey": "customValue",
      "displayNotes": {"key": "value"}
  },
  "merchantId": "MID-XXXX-XXX",
  "failureRedirect": false,
  "brandColor": "#FF5733",
  "defaultMethod": "card",
  "description": "Payment for order ORD123456",
  "manualCapture": false,
  "customer": {
      "email": "john@example.com",
      "reference": "894321"
  },
  "saveCard": "optional",
  "retrieveSavedCard": true,
  "interactionSource": "ECOMMERCE",
  "enable3DS": true,
  "serverWebhook": "https://your_webhook_url",
  "notes": "Special handling required"
}'
```

<Callout type="warn" title="Two values to fill in before you send">
  `merchantId` is required and the panel cannot guess it. Save your test **merchant ID** next to your keys in the banner above and it is substituted into the `MID-XXXX-XXXX` placeholder automatically. Until you do, the panel treats the placeholder as an unfilled parameter and keeps Send disabled rather than firing a request the API is certain to reject.

  Change `order` as well. It is your own reference for the payment and Kashier rejects a duplicate order reference for the same merchant (`ERR_ORD_02`), so leaving the placeholder in place collides with everyone else who left it alone.
</Callout>

<ApiTryIt
  method="POST"
  url="https://test-api.kashier.io/v3/payment/sessions"
  body="{
  expireAt: '2030-01-01T00:00:00.000Z',
  maxFailureAttempts: 3,
  paymentType: 'credit',
  amount: '100.00',
  currency: 'EGP',
  order: 'REPLACE-WITH-YOUR-OWN-REFERENCE',
  merchantRedirect: 'https://your-website.com/redirect',
  display: 'en',
  type: 'one-time',
  allowedMethods: 'card,wallet',
  merchantId: 'MID-XXXX-XXXX',
  description: 'Docs playground test',
  customer: { email: 'docs-check@example.com', reference: 'docs-check-1' },
}"
/>

### Headers [#headers]

| Key           | Description                                                                                                                                                                                                                   |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication).                                    |
| api-key       | You can obtain your `api-key` from the merchant dashboard under the **Integrations** section. Please note that the API key is different for the *live* and *test* environments, so make sure to use the correct one for each. |

Full parameter and response reference → [Create payment session](/docs/api-reference/payment-sessions/createPaymentSession).

## Step 2: Send the customer to pay [#step-2-send-the-customer-to-pay]

<Callout title="Note">
  sessionUrl is the URL that will be used to redirect the customer to the payment page. You can use it as the src attribute in a link or `<iframe>`.
</Callout>

The response to the create call includes a `sessionUrl`. You have two options for getting the customer to it:

* **Redirect** — send the customer's browser straight to `sessionUrl`. Kashier handles the checkout UI and returns them to your `merchantRedirect` URL afterward.
* **Embed** — render `sessionUrl` inside your own page in an iframe instead of redirecting away:

```html
<iframe src="https://payments.kashier.io/session/67adc07584f10c00121f6739?mode=test"></iframe>
```

This embed flow was formerly documented as the "Payment UI Builder".

<Callout title="Note">
  The legacy documentation page hosted an interactive test builder that generated a checkout from the fields below. Use the [create payment session](#step-1-create-a-payment-session) call with these fields to build your checkout.
</Callout>

#### Merchant details [#merchant-details]

* **Merchant ID**
* **API key**
* **Secret key**

#### Order details [#order-details]

* **Order ID** — unique order reference between merchant and Kashier
* **Amount**
* **Currency**

#### Payment method [#payment-method]

Select the payment methods to enable:

* Card
* Wallet
* Bank installment
* Buy now pay later

#### Redirect settings [#redirect-settings]

* **Redirect URL**
* **Redirect method** — Get or Post
* **Redirect failure** — True or False

#### Display settings [#display-settings]

* **Display language** — English or Arabic
* **Display mode** — Test or Live
* **Brand color** — e.g. `#00bcbc`

### Getting the result out of an embedded checkout [#getting-the-result-out-of-an-embedded-checkout]

When you embed rather than redirect, the customer never leaves your page, so there is no redirect landing you can read the outcome from. The checkout talks back to the page that hosts it with `window.postMessage`. Add a `message` listener on your page and switch on `e.data.message`.

What you receive depends on how you embedded it.

**If you embed with Kashier's checkout script**, the script sits between the checkout and your page and re-emits a small, clean set:

| `e.data.message` | Emitted when                                          | What you do                                                                                                                                                    |
| ---------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `iframeLoaded`   | The checkout is loaded and its pay button is ready.   | Drop your own loading state.                                                                                                                                   |
| `paymentSuccess` | The payment succeeded.                                | Show your success state.                                                                                                                                       |
| `iframeHide`     | The customer closed or dismissed the checkout.        | Remove your overlay and re-enable the page.                                                                                                                    |
| `urlRedirection` | The checkout is ready to return to your redirect URL. | Nothing. The script redirects the top window for you — it builds an auto-submitting form when `redirectMethod` is `post`, and otherwise replaces the location. |

**If you render `sessionUrl` in your own `<iframe>`**, as in the snippet above, there is no script in between and you receive the checkout page's own messages directly:

| `e.data.message`        | Payload                                  | Meaning                                                                                                                       |
| ----------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `contentLoaded`         | `null`                                   | The checkout DOM is ready.                                                                                                    |
| `paymentSuccess`        | `{}`                                     | The payment succeeded.                                                                                                        |
| `urlRedirection`        | `{ redirectUrl, redirectMethod }`        | The checkout wants the top window sent to `redirectUrl`. With no script in between, **performing this redirect is your job**. |
| `closeIframe`           | `null`                                   | The customer closed the checkout. Tear down your overlay.                                                                     |
| `success` / `failure`   | Gateway data                             | Raw card-form outcome callbacks.                                                                                              |
| `merchantStoreRedirect` | Full gateway response plus `redirectUrl` | The 3DS result coming back from the challenge.                                                                                |

A minimal listener for the raw-iframe case:

```js
window.addEventListener('message', (e) => {
  switch (e.data?.message) {
    case 'contentLoaded':
      // checkout is ready — hide your spinner
      break;
    case 'paymentSuccess':
      // show a provisional success state, then confirm server-side
      break;
    case 'closeIframe':
      // customer dismissed the checkout — remove your overlay
      break;
    case 'urlRedirection':
      window.location.replace(e.data.params.redirectUrl);
      break;
  }
});
```

<Callout type="warn" title="Never treat paymentSuccess as settlement">
  `paymentSuccess` arrives from the browser and can be forged, replayed, or simply lost if the customer closes the tab. Confirm every payment server-side — from your [webhook](/docs/webhooks) or by calling [Verify the result](#step-3-verify-the-result) — before you release goods. The same goes for the redirect query string, whose `signature` you must validate.
</Callout>

<Callout type="warn" title="Test mode only ever offers card and wallet">
  Outside live mode the checkout ignores `allowedMethods` and forces the method list to card and wallet. `bank_installments`, `fawry` and the rest cannot be exercised in the sandbox at all. So a method working in test tells you nothing about live, and a method being missing in test does not mean your account is misconfigured — verify method availability against your live account.
</Callout>

## Step 3: Verify the result [#step-3-verify-the-result]

| Endpoint | Value                                                                                                                                    |
| -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| TEST-URL | [https://test-api.kashier.io/v3/payment/sessions/:sessionId/payment](https://test-api.kashier.io/v3/payment/sessions/:sessionId/payment) |
| LIVE-URL | [https://api.kashier.io/v3/payment/sessions/:sessionId/payment](https://api.kashier.io/v3/payment/sessions/:sessionId/payment)           |
| Method   | GET                                                                                                                                      |

```bash
curl --location 'https://test-api.kashier.io/v3/payment/sessions/:sessionId/payment' \
  --header 'Authorization: YOUR_TEST_SECRET_KEY'
```

<ApiTryIt method="GET" url="https://test-api.kashier.io/v3/payment/sessions/:sessionId/payment" sendApiKey="false" />

### Headers [#headers-1]

| Key           | Description                                                                                                                                                                                |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Authorization | The Authorization is a secret key that is used to identify the merchant. You can obtain it from Kashier's dashboard. Learn more about [Authorization](/docs/dashboard-api/authentication). |

Full parameter and response reference → [Get payment session](/docs/api-reference/payment-sessions/getPaymentSession).

## Next steps [#next-steps]

<Cards>
  <Card title="Webhooks" href="/docs/webhooks" description="Get server-to-server notifications instead of polling for the result." />

  <Card title="Refunds" href="/docs/accept-payments/refunds" description="Make full or partial refund requests against an order." />

  <Card title="Currency conversion" href="/docs/accept-payments/currency-conversion" description="Look up live exchange rates for cross-currency pricing." />

  <Card title="Product catalog" href="/docs/accept-payments/products" description="Build itemized payment links and checkout pages from products instead of a flat amount." />
</Cards>
