Skip to content

Hosted checkout flow

Download .md

The hosted checkout is the standard way to accept a card payment. Paymee hosts the payment page and handles 3-D Secure, so card data never reaches your servers.

  1. Create a session — POST /gateway/sessions/ with the amount, the buyer, and your URLs. The response includes a checkout_url.
  2. Redirect the buyer to checkout_url.
  3. The buyer pays on the Paymee-hosted page (with 3-D Secure). After a decline they can try again on the same page, up to max_attempts times. See The payment page.
  4. Paymee sends a webhook to your webhook_url with the authoritative result. Fulfil the order here, not on the browser redirect.
  5. After a successful payment, the buyer is redirected to your return_url.
sequenceDiagram
    participant S as Your server
    participant P as Paymee
    participant B as Buyer
    S->>P: POST /gateway/sessions/
    P-->>S: { checkout_url }
    S->>B: redirect to checkout_url
    B->>P: pays on hosted page (3-D Secure)
    P->>S: webhook session.succeeded (authoritative) → fulfil
    P->>B: redirect to return_url

POST https://api.paymee.online/api/v1/gateway/sessions/

Field Type Required Notes
amount string ✅ Decimal string with the currency’s exact number of decimals: 3 for TND, e.g. "10.500". See Amounts & currency.
currency string – ISO 4217 code. Defaults to your account’s currency (TND).
merchant_reference string (≤ 200) ✅ Your unique reference for this payment, e.g. your order id. See Idempotency.
first_name string (≤ 100) ✅ Buyer’s given name.
last_name string (≤ 100) ✅ Buyer’s surname.
email string (≤ 255) ✅ Buyer’s email.
phone string (≤ 20) – Buyer’s phone.
description string (≤ 500) – What the buyer is paying for.
order_id string (≤ 100) – Your display-friendly order id.
items array of objects – Line items, e.g. [{ "name": "T-shirt", "quantity": 2, "unit_price": "25.000" }]. Returned as you sent them.
metadata object – Your own key/values, returned on the session and every webhook. Up to 50 keys; keys up to 40 characters, without [ or ]; values up to 500 characters. Numbers and booleans are stored as text (42 → "42"); objects and lists are refused.
return_url string (≤ 2048) – Where the buyer is sent after a successful payment. See Returning the buyer.
cancel_url string (≤ 2048) – Where the buyer goes back to your store without paying.
webhook_url string (≤ 2048) – Where Paymee sends this session’s events. Defaults to your account’s endpoint. See Webhooks.
max_attempts integer – Payment attempts allowed on the session, 1–10 (default 5).
locale string – Language of the payment page: auto (default), fr or en. See Language.

All three URLs must use https:// on a domain name; webhook_url must not be an IP address. An optional text field sent as "" counts as not given.

See the full schema in the API Reference → Create a payment session.

201 Created, with the session:

{
"id": "ps_0b7c3a1e-5d2f-4e8a-9c6b-1f2e3d4c5b6a",
"merchant_reference": "order-0001",
"order_number": "PM-014A2C",
"order_id": "",
"amount": "10.500",
"currency": "TND",
"original": { "amount": "10.500", "currency": "TND", "rate": "1.000000" },
"description": "",
"items": [],
"status": "CREATED",
"livemode": true,
"buyer": {
"first_name": "Aymen",
"last_name": "Ben Salah",
"email": "aymen@example.com",
"phone": ""
},
"metadata": { "cart_id": "8812" },
"return_url": "https://your-site.tn/orders/order-0001/paid",
"cancel_url": "https://your-site.tn/cart",
"webhook_url": "https://your-site.tn/webhooks/paymee",
"max_attempts": 5,
"checkout_url": "https://pay.paymee.online/checkout/kq2V8Zr3xN0bT6mW1yPdF4sHjL9cA7uE",
"locale": "auto",
"expires_at": "2026-11-21T10:24:00.000000Z",
"completed_at": null,
"created_at": "2026-11-20T10:24:00.000000Z",
"attempts": [],
"refunds": []
}

buyer is the same object as in webhooks. Each entry of attempts has failure_code and failure_message — the acquirer’s decline code and reason, as in webhooks — when that attempt was declined.

Redirect the buyer to checkout_url exactly as returned. Sending the same merchant_reference again returns this session with 200 OK instead of creating a second one — see Idempotency.

checkout_url opens Paymee’s payment page, where the buyer pays by card (Visa or Mastercard).

  • Two pages, one flow. Paymee’s page shows the amount, a language switcher, the result and the buttons back to your store. The card is entered on the page of ClicToPay, the acquiring bank, so card details never reach your servers.
  • 3-D Secure on every payment. The buyer’s bank checks that the card is theirs: it may ask for a code by SMS or an approval in its app, or approve without asking anything.
  • A decline is not the end. The buyer stays on the page and can try again, with the same card or another, up to max_attempts times (default 5). See Session status.

The payment page is in French or English. Choose its language with locale:

locale The page opens in
auto (default) The buyer’s browser language: French for a French browser, English for any other.
fr French.
en English.

The buyer can switch language on the page; their choice wins over locale and is kept on that browser for later payments. The card-entry page opens in the language the buyer is reading when they click Pay.

On a website, send the buyer to checkout_url in the same tab or a new one. The payment page can’t be shown in an iframe: browsers refuse to frame it.

In a mobile app, open checkout_url in an in-app browser tab: SFSafariViewController on iOS, Custom Tabs on Android. The buyer stays in your app, the page shows its real address, and banks’ 3-D Secure steps work as in a browser, including a hand-off to the bank’s own app.

To bring the buyer back to your app, make return_url and cancel_url links your app opens: a universal link on iOS or an App Link on Android, on your own domain (for example https://your-site.tn/app/orders/order-0001/paid). When the app opens, read the order’s state from your server, which the webhook keeps up to date.

Paymee doesn’t add anything to your URLs: the buyer lands on exactly the return_url or cancel_url you gave. Put what you need to show the right page in the URL itself, for example https://your-site.tn/orders/order-0001/paid.

  • return_url — after a successful payment, the hosted page shows a short confirmation, then redirects the buyer here. Without one, the buyer stays on the confirmation.
  • cancel_url — the hosted page shows a Return to store button linking here: while paying, after a decline, and when the link or the session has expired. Without one, the page tells the buyer they can close the window.

A declined buyer is not redirected: they stay on the hosted page and can try again while attempts remain.

A session moves through these statuses (status field):

Status Meaning
CREATED Created; no payment attempt yet.
PROCESSING A payment attempt is in progress (the buyer is on the card or 3-D Secure page).
PENDING An attempt was declined; the buyer can try again.
SUCCEEDED Paid. Final.
FAILED Every allowed attempt was declined. Final.
EXPIRED Not paid within the session’s lifetime (about 24 hours). Final.
CANCELED Canceled by your cancel call. Final.
stateDiagram-v2
    direction LR
    [*] --> CREATED
    CREATED --> PROCESSING: payment attempt
    PROCESSING --> SUCCEEDED: approved
    PROCESSING --> PENDING: declined, attempts left
    PENDING --> PROCESSING: buyer tries again
    PROCESSING --> FAILED: declined, no attempts left
    PENDING --> FAILED: no attempts left
    CREATED --> CANCELED: your cancel call
    PENDING --> CANCELED: your cancel call
    CREATED --> EXPIRED: lifetime over
    PENDING --> EXPIRED: lifetime over
    PROCESSING --> EXPIRED: lifetime over
    SUCCEEDED --> [*]
    FAILED --> [*]
    CANCELED --> [*]
    EXPIRED --> [*]

The buyer can’t cancel a session: one they abandon stays open until it expires. Each final status has its webhook (session.succeeded, session.failed, session.expired) except CANCELED, which only your own call can cause.

Fetch a session at any time with GET /gateway/sessions/{id}/. It includes its payment attempts and its refunds. Webhooks are the primary mechanism; fetching is the backup and the source of truth for reconciliation.

List your sessions, newest first, with GET /gateway/sessions/list/:

Terminal window
curl "https://api.paymee.online/api/v1/gateway/sessions/list/?limit=50" \
-H "Authorization: Bearer sk_live_…"

The response is { "data": [ …sessions… ], "has_more": true }. limit is 1–100 (default 20). To get the next page, pass the last session’s id as starting_after; ending_before pages backwards.

Cancel a session that hasn’t been paid with POST /gateway/sessions/{id}/cancel/. The response is the session, now CANCELED, and its checkout link stops working.

  • Only CREATED and PENDING sessions can be canceled. While a payment attempt is PROCESSING, the call is refused with 422 invalid-state: wait for the attempt to finish.
  • A session that is already final answers 409 session-terminal.