Create hosted checkout session
API contract v1.0.0
Creates a hosted checkout session linked to the order's Payment. Identify the order in one of two ways: pass payment_id, or pass merchant_order_no + amount + currency and the platform creates or reuses a Payment. Sending both groups of identity parameters, or neither, returns 400. When you reuse an existing order, a mismatched amount or currency returns 422. If the linked Payment has already succeeded, the API returns 200 and a read-only session with status=completed, and doesn't create a new entry point for charging. Any other state in which the order can't be paid returns 409. The request contains no card data, return_url and cancel_url must match a registered redirect URL rule, and the amount, currency and receiving merchant can't be changed through URL parameters. If you use a new idempotency key with the same order ID, amount and currency, the platform creates a new checkout session for the same Payment and doesn't create a second Payment.
Request headers
| Request headers | Required | Format | Description |
|---|---|---|---|
| Authorization | Yes | Bearer <API_KEY> | Server-side key, to be used only on the merchant's server. The merchant and environment are derived from the key. A missing, invalid, revoked or expired key returns 401. |
| Content-Type | Yes | application/json | The request body is UTF-8 encoded JSON. |
| Idempotency-Key | Yes | 1–255 visible ASCII characters | Idempotency key, 1–255 visible ASCII characters. The scope is merchant, environment, method and path: with the same key and matching core business parameters, the original business object is reused, and conflicting parameters return 409 idempotency_conflict. Use one key per submission, and reuse the same key when you retry after a network error; use a new key for a new submission, such as a card change. For concurrent requests with the same key, only one request gets to run, and the others wait for its transaction to commit and then return the same object (200). If the wait times out, the API returns 409 operation_in_progress (retryable=true); retry later with the original key. A request rejected with 400, 401, 403 or 422 doesn't consume the idempotency key; fix it and submit with a new key. |
Request body
JSON object CreateCheckoutSessionRequest
| Name | Type | Required | Description |
|---|---|---|---|
payment_id | string | Condition: Send either this or ( merchant_order_no + amount + currency), not both | An existing Payment. When you pass it, don't also pass merchant_order_no, amount or currency. Constraints: Example: |
merchant_order_no | string | Condition: Required together with amount and currency when payment_id isn't sent | Merchant order ID. When submitted together with amount and currency, the platform creates or reuses the Payment for this order. Constraints: Example: |
amount | integer(int64) | Condition: Required together with merchant_order_no and currency when payment_id isn't sent | Order amount, in the smallest currency unit. When you reuse an existing order, it must match the original order. Constraints: Example: |
currency | string | Condition: Required together with merchant_order_no and amount when payment_id isn't sent | Currency. When you reuse an existing order, it must match the original order. Constraints: Example: |
description | string | No | The order summary shown on the checkout page. Constraints: Example: |
return_url | string(uri) | Required | The merchant's return URL after the payment ends. It must match a redirect URL rule registered for the current environment: the match covers the scheme, host (exact or one-level wildcard), port and path prefix (by path segment), and the query string and fragment aren't part of the match. When the platform redirects the payer back, it uses this URL exactly as provided and appends no parameters. Constraints: Example: |
cancel_url | string(uri) | No | The "Return to merchant" URL. It must match a redirect URL rule registered for the current environment: the match covers the scheme, host (exact or one-level wildcard), port and path prefix (by path segment), and the query string and fragment aren't part of the match. Clicking "Return to merchant" doesn't cancel the charge. Constraints: Example: |
locale | string | No | Checkout page language (BCP 47): a value starting with zh shows Chinese and one starting with en shows English; for any other value, or if you don't pass one, the merchant's default language is used. Constraints: Example: |
Request example
Create a checkout session for an existing Payment
curl -X POST 'https://api.fluxan.co/v1/checkout/sessions' \
-H 'Authorization: Bearer <TEST_SERVER_API_KEY>' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: <UNIQUE_OPERATION_KEY>' \
-d '{
"payment_id": "pay_demo_008",
"return_url": "https://merchant.example/payment/return",
"cancel_url": "https://merchant.example/payment/cancel",
"locale": "zh-CN"
}'Placeholders to replace
<TEST_SERVER_API_KEY>: Test-environment server API key, created on the "API Key" page of the merchant portal<UNIQUE_OPERATION_KEY>: The idempotency key you generate for this request (for example, a UUID); reuse the same key when you retry after a network error
Create from order info (the platform creates or reuses a Payment)
curl -X POST 'https://api.fluxan.co/v1/checkout/sessions' \
-H 'Authorization: Bearer <TEST_SERVER_API_KEY>' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: <UNIQUE_OPERATION_KEY>' \
-d '{
"merchant_order_no": "ORDER-DEMO-008",
"amount": 10000,
"currency": "USD",
"description": "Example hosted checkout order",
"return_url": "https://merchant.example/payment/return",
"cancel_url": "https://merchant.example/payment/cancel",
"locale": "zh-CN"
}'Placeholders to replace
<TEST_SERVER_API_KEY>: Test-environment server API key, created on the "API Key" page of the merchant portal<UNIQUE_OPERATION_KEY>: The idempotency key you generate for this request (for example, a UUID); reuse the same key when you retry after a network error
Note
Server API keys must only be used on your own server, so the docs site doesn't offer interactive API testing. Get a test key: API Key page in the merchant portal (opens the merchant portal in a new tab) (Test environment; Requires Owner, Admin or Developer)
Responses
An idempotent replay with the same key, or the read-only session returned when the linked Payment has already succeeded.
{
"id": "cs_demo_002",
"payment_id": "pay_demo_008",
"status": "completed",
"checkout_url": "https://pay.payhub.example/checkout/<SHORT_LIVED_TOKEN>",
"expires_at": "2026-09-23T09:30:00Z",
"return_url": "https://merchant.example/payment/return",
"cancel_url": "https://merchant.example/payment/cancel",
"locale": "zh-CN",
"created_at": "2026-09-23T09:20:00Z",
"request_id": "req_demo_019"
}Placeholders to replace
<SHORT_LIVED_TOKEN>: Short-lived credential returned by the platform; use it as is and don't log it
Response fields
Returns an object. See the object section for field descriptions.
| Name | Type | Required | Description |
|---|---|---|---|
id | string | Required | Checkout session ID. Example: |
payment_id | string | Required | The related Payment. Example: |
status | string | Required | Checkout session status.
Example: |
checkout_url | string(uri) | Required | The checkout page URL. It carries a short-lived credential bound to this session only, and contains no merchant server-side key. Use it only for a full-page redirect of the payer's browser: don't write it to logs, analytics tools or error reporting, and don't share it with anyone. Example: |
expires_at | string(date-time) | Required | The time the session expires, fixed at creation. A session lasts at most 30 minutes (the platform can shorten this) and never longer than the time the order has left to be paid. During the read-only period after expiry (fixed at creation; 24 hours by default, which is also the maximum), the payer can still use the original link to view the session and the payment result (read-only, no submissions). After that, the link stops working. Example: |
return_url | string(uri) | Required | The merchant's return URL after the payment ends. Example: |
cancel_url | string(uri)Nullable | Required | The URL the payer goes to when they click "Return to merchant". Clicking it doesn't cancel a charge that was already submitted. It's null if not provided. Example: |
locale | string | Required | Checkout page language. Example: |
created_at | string(date-time) | Required | Creation time. Example: |
request_id | string | Required | Unique identifier of this request, used for troubleshooting and support. Example: |
Possible error codes
| HTTP | Error code | When it occurs | How to handle |
|---|---|---|---|
| 400 | invalid_request | Idempotency-Key is missing; the order ID contains invalid characters; payment_method_data and payment_method_id are submitted together; confirm=false carries card details; confirm=true has no return_url; a checkout session request sends both groups of identity parameters, or neither; a browser_info / billing_details field the provider requires is missing; the cursor, time format or limit is invalid | Fix the request according to error.field and resubmit it; don't charge again automatically |
| 400 | sensitive_data_rejected | Digits that look like a card number, or similar data, appear in description or metadata | Remove the sensitive content and resubmit; check why your system wrote card data into these fields |
| 403 | capability_disabled | Direct card isn't enabled; the payment or refund request capability has been turned off; the currency isn't enabled | Use hosted checkout instead, or contact platform Operations |
| 403 | merchant_not_active | The merchant has been suspended or closed; callbacks, queries and notifications for business that was already accepted aren't affected | Contact platform Operations |
| 404 | not_found | An incorrect ID; using a test key to look up a live object; looking up a platform exception refund (not returned by the open API) | Check the ID and the environment of the key you used; the platform doesn't reveal whether another merchant's object exists |
| 409 | idempotency_conflict | The key was reused but the amount, currency, order ID or similar parameters differ | This is an integration bug: check your idempotency key generation logic; don't change the parameters and replay the original key; use a new key for a new operation |
| 409 | operation_in_progress | Requests with the same key were sent concurrently | Retry later with the same key, or retrieve related_resource_id; don't switch to a new key |
| 409 | invalid_state | Canceling a payment that is processing / succeeded; calling confirm on an order that has expired, been canceled or already succeeded; creating a checkout session for an order that can't be paid | Retrieve the object's latest state, then decide what to do |
| 409 | attempt_limit_reached | The default maximum is 3 attempts, and the platform can lower it | This order can't be charged again; once you've confirmed the original payment is closed, use a new order ID |
| 422 | order_amount_mismatch | The amount or currency of the same order ID changed when creating a checkout session | The amount can't be changed: close the original payment, then create a new one with a new order ID |
| 422 | currency_not_supported | The currency isn't in the overlap between the merchant's configuration and the provider's capabilities | Use an enabled currency or contact platform Operations |
| 422 | amount_out_of_range | Below the minimum or above the maximum amount per payment, or over the daily cumulative limit; the error doesn't return the specific thresholds | Adjust the amount or contact the platform |
| 422 | url_not_allowed | return_url / cancel_url doesn't match a registered redirect URL rule | Register it on the redirect URL allowlist page in the merchant portal, then resubmit |
| 503 | temporarily_unavailable | The platform has paused new transactions on this channel; the response doesn't give the internal reason | Retry later with the original idempotency key; this request created no Payment or charge; if it keeps happening, contact platform Operations |
| 401 | unauthenticatedCommon | No Authorization header was sent; the key was copied incompletely; the old key expired after a rotation; the key was revoked | Check your credentials: on the API Key page in the merchant portal, verify the key's status and environment |
| 403 | forbiddenCommon | Creating a refund with a read-only key; calling getEvent without the read scope for the event's business area; if the request's source IP isn't on the key's allowlist, the more specific ip_not_allowed is returned | Use a key that has the required scope; don't work around the restriction |
| 429 | rate_limitedCommon | A rate limit per merchant and per key; switching keys doesn't get around the merchant's overall limit | Wait for Retry-After seconds, then retry with the original idempotency key |
| 500 | internal_errorCommon | — | Retry with the original idempotency key, or query by merchant order ID / refund ID; never charge again with a new key |
| 502 / 503 | upstream_unavailableCommon | A provider or platform dependency failed, and the platform can't confirm whether this request took effect | Same as above |
Notes
- Identify the order in one of two ways:
payment_id, ormerchant_order_no+amount+currency. Sending both or neither returns 400. - When you reuse an existing order, a mismatched amount or currency returns 422
order_amount_mismatch. If the linked payment has already succeeded, the API returns 200 and a read-only session withstatus=completed, and doesn't create a new entry point for charging. Any other state in which the order can't be paid returns 409. - If you use a new idempotency key with the same order details, the platform creates a new checkout session for the same payment and doesn't create a second payment.
return_urlandcancel_urlmust match a registered redirect URL rule: Register redirect URLs (opens the merchant portal in a new tab) (Test environment; Requires Owner, Admin or Developer).- Changing URL parameters can't change the amount, the currency or the merchant that receives the payment.
Warning
checkout_url contains a checkout token. Use it only for a full-page redirect of the payer's browser. Don't write it to logs, analytics tools or error reporting, and don't share it with anyone.