Checkout
Overview
The Checkout flow is a session-based hosted payment flow. A merchant first creates a checkout session through the authenticated API, then opens the returned checkout URL for the shopper. The shopper-facing application uses the public checkout endpoints to fetch the session, show allowed payment methods, optionally show reusable tokens, create a new token when needed, and start the payment.
The flow is centered around these endpoints:
- POST /api/v1/checkout/sessions - create or reuse a checkout session
- GET /api/v1/checkout/sessions/{public_id} - merchant-side session status lookup
- GET /api/v1/checkout/public/sessions/{public_id}?t={token} - shopper-side session payload
- POST /api/v1/checkout/public/sessions/{public_id}/pay?t={token} - start a payment attempt
- POST /api/v1/checkout/public/sessions/{public_id}/tokens?t={token} - create a reusable payment token
- GET /api/v1/checkout/public/sessions/{public_id}/tokens?t={token} - get reusable tokens allowed for the session
- GET /checkout/return/{public_id} - payment provider return endpoint
- GET /checkout/token/return/{public_id} - token creation return endpoint
High-level flow
- Merchant backend creates a checkout session.
- API returns publicId, checkoutUrl, and expiresAt.
- Shopper is redirected to checkoutUrl.
- Checkout frontend loads the public session using the publicId and the public token from the t query parameter.
- Checkout frontend optionally loads available reusable payment tokens.
- Shopper either pays directly, pays with an existing token, or creates a new token first.
- Provider redirects back through Paymaxx return endpoints.
- Paymaxx updates session state and redirects the shopper either back to the merchant return URL or back to hosted checkout for retry.
Authentication and public access
Merchant endpoints use Bearer authentication, same as the rest of the API. Public checkout endpoints do not use Bearer auth. Instead, they require both:
- {public_id} in the path
- t query parameter containing the public checkout token
If either value is missing or invalid, the public checkout middleware returns 404 CHECKOUT_SESSION_NOT_FOUND. If the session is expired, the middleware returns 410 CHECKOUT_SESSION_EXPIRED.
1. Create checkout session
Endpoint: POST /api/v1/checkout/sessions
This endpoint creates a new hosted checkout session. It is idempotent by request_id per merchant: when the same merchant sends the same request_id again, the existing session is returned instead of creating a second one.
Request body
{
"request_id": "0d67d6df-45d2-4d8a-9d2e-9e8f1cc4f9cb",
"reference": "ORDER-100045",
"currency": "EUR",
"amount": 2599,
"locale": "en",
"shop_country": "NL",
"payment_token_mode": "optional",
"return_url": "https://merchant.example/checkout/return",
"webhook_url": "https://merchant.example/webhooks/payments",
"payer_data": {
"email": "john.doe@example.com",
"firstName": "John",
"lastName": "Doe"
},
"order_data": {
"description": "Order ORDER-100045",
"items": [
{
"name": "T-shirt",
"category": "apparel",
"brand": "Paymaxx",
"description": "Black shirt size M",
"imageUrl": "https://merchant.example/assets/tshirt-black-m.jpg",
"quantity": 1,
"amount": 2599
}
],
"metadata": {
"cartId": "cart-100045"
}
},
"pricing_data": {
"currency": "EUR",
"subTotal": 2599,
"feeTotal": 0,
"discountTotal": 0,
"grandTotal": 2599
},
"discounts_data": [],
"allowed_methods": [
"creditcard",
"paypal",
"ideal"
],
"available_token_ids": {
"tokenIds": [
"tok_1",
"tok_2"
],
"defaultTokenId": "tok_1"
}
}
Field notes
- request_id is required and must be a UUID.
- amount is in minor units, for example 2599 = EUR 25.99.
- currency must be a 3-character currency code.
- return_url is required and is used after a successful hosted checkout completion or after session expiry.
- webhook_url is optional and is forwarded into the payment/token flows where supported.
- payment_token_mode supports none, optional, and required.
- allowed_methods is optional. When provided, every method in the list must already be enabled for the merchant.
- available_token_ids is optional. It can be a plain list of token ids or an object with tokenIds and optional defaultTokenId.
- order_data.items is required and must contain at least one line.
- pricing_data is required and is exposed again on the public session payload for rendering the checkout summary.
Validation currently enforced by backend
- reference: optional, string, max 255
- locale: optional, string, max 16
- shop_country: optional, 2-letter country code
- order_data.items.*.name: required, string, max 100
- order_data.items.*.category: required, string, max 100
- order_data.items.*.brand: required, string, max 100
- order_data.items.*.description: optional, string, max 2000
- order_data.items.*.imageUrl: optional, string, max 2048
- order_data.items.*.quantity: required, integer, min 1
- order_data.items.*.amount: required, integer, min 0
Response
{
"publicId": "c6621e13fe7340a2a734b3d6ff7d9e7c4d5d",
"checkoutUrl": "https://checkout.example/c/c6621e13fe7340a2a734b3d6ff7d9e7c4d5d?t=G8j...",
"expiresAt": "2026-03-11T15:30:00+00:00"
}
Checkout URLs are built as {hosted_base_url}/c/{public_id}?t={token}. The session currently expires after 30 minutes.
2. Get checkout session status from merchant backend
Endpoint: GET /api/v1/checkout/sessions/{public_id}
This is the merchant-side lookup endpoint. It does not expose the full shopper payload. It is mainly used to check current session state and see whether a transaction attempt has already been attached to the session.
Response
{
"publicId": "c6621e13fe7340a2a734b3d6ff7d9e7c4d5d",
"status": "processing",
"transactionId": 123456,
"expiresAt": "2026-03-11T15:30:00+00:00",
"reference": "ORDER-100045",
"currency": "EUR",
"amount": 2599
}
3. Load public checkout session
Endpoint: GET /api/v1/checkout/public/sessions/{public_id}?t={token}
This endpoint is meant for the hosted checkout frontend. It returns the display payload needed to render order summary, pricing, branding, localization data, allowed payment methods, and allowed payment tokens.
Response
{
"id": "c6621e13fe7340a2a734b3d6ff7d9e7c4d5d",
"status": "active",
"expiresAt": "2026-03-11T15:30:00+00:00",
"locale": "en",
"payer": {
"email": "john.doe@example.com"
},
"order": {
"description": "Order ORDER-100045",
"items": []
},
"pricing": {
"currency": "EUR",
"subTotal": 2599,
"feeTotal": 0,
"discountTotal": 0,
"grandTotal": 2599
},
"discounts": [],
"branding": {
"logoUrl": "https://merchant.example/logo.svg",
"primaryColor": "#111827",
"secondaryColor": "#F9FAFB",
"privacyPolicyUrl": "https://merchant.example/privacy",
"termsUrl": "https://merchant.example/terms",
"merchantDisplayName": "Merchant demo",
"supportEmail": "support@merchant.example",
"supportUrl": "https://merchant.example/support"
},
"allowedMethods": [
"creditcard",
"paypal"
],
"availableTokens": {
"tokenIds": [
"tok_1"
],
"defaultTokenId": "tok_1"
},
"paymentTokenMode": "optional",
"returnUrl": "https://merchant.example/checkout/return"
}
Branding and localization notes
- locale comes from the session and should be used by the checkout frontend for translations and formatting.
- shop_country is stored on the session and passed into payment creation, where supported by the provider.
- branding is built by merging merchant dashboard branding with any session-level branding stored on the session details.
- The public payload can expose logoUrl, logoAssetId, primaryColor, secondaryColor, privacyPolicyUrl, termsUrl, merchantDisplayName, supportEmail, and supportUrl.
- At the moment, merchant dashboard branding is already consumed automatically by checkout. The current checkout session request validation does not yet document a dedicated branding_data input even though the public resource supports merged branding output.
4. Get available reusable payment tokens
Endpoint: GET /api/v1/checkout/public/sessions/{public_id}/tokens?t={token}
This endpoint resolves the token ids saved in the checkout session snapshot and returns only tokens that belong to the same merchant, are confirmed, and are not cancelled.
Response
[
{
"tokenId": "tok_1",
"method": "creditcard",
"label": "Visa •••• 1111",
"expires": "12/2028",
"payer": {
"email": "john.doe@example.com"
}
}
]
The label is derived from token details where possible. For cards, the backend tries to build a shopper-friendly label from brand and last 4 digits.
5. Create a reusable payment token from checkout
Endpoint: POST /api/v1/checkout/public/sessions/{public_id}/tokens?t={token}
This endpoint is available only when payment_token_mode is optional or required. It starts a token registration flow and returns a payer URL.
Request body
{
"paymentMethod": "creditcard",
"reference": "ORDER-100045",
"description": "Save card for future use",
"payer": {
"email": "john.doe@example.com"
}
}
Request notes
- paymentMethod is required.
- reference and description are optional.
- If no payer is provided, checkout reuses the payer data stored on the session.
- If the payment method is outside allowed_methods or not enabled on the merchant, the request fails.
- For MiPay credit card tokenization, the backend automatically adds a 3DSecure payload based on payer, currency, and amount.
Response
{
"payerUrl": "https://provider.example/tokenize/abc123",
"widgetUrl": "https://provider.example/widget/token/abc123"
}
Depending on provider, the response may contain only payerUrl or both payerUrl and widgetUrl.
6. Pay checkout session
Endpoint: POST /api/v1/checkout/public/sessions/{public_id}/pay?t={token}
This endpoint starts a payment attempt for the checkout session. It supports both direct payment and pay-with-token.
Direct payment example
{
"payment_method": "ideal",
"pay_request_id": "7e7bc53b-d34f-4410-a0df-d36917136b2d",
"payer_updates": {
"email": "john.doe@example.com"
}
}
Pay with existing token example
{
"payment_method": "creditcard",
"payment_token_id": "tok_1",
"pay_request_id": "af03ec6f-0091-4c0e-95cc-1554871c17df"
}
PayPal example
{
"payment_method": "paypal",
"pay_request_id": "5aa7a4ea-26d3-4998-b38e-f73a271be3b4",
"paypal": {
"experienceContext": {
"shippingPreference": "GET_FROM_FILE"
}
}
}
Request notes
- payment_method is required in the request schema.
- payment_token_id is optional. When present, the backend validates that the token is allowed by the session snapshot and is usable.
- pay_request_id is optional but recommended. It acts as per-click idempotency for payment attempts.
- payer_updates is optional and is merged over the stored session payer before payment creation.
- paypal is optional and is passed into the payment payload when PayPal is used.
- If payment_token_mode is required, direct payment without a token is rejected.
- If payment_token_mode is none, payment with payment_token_id is rejected.
Response when redirect is required
{
"payerUrl": "https://provider.example/pay/abc123",
"widgetUrl": "https://provider.example/widget/abc123"
}
Response when payment is already terminal or completed synchronously
{
"id": "af03ec6f-0091-4c0e-95cc-1554871c17df",
"status": "success",
"method": "creditcard",
"currency": "EUR",
"amount": 2599,
"balance": 0,
"details": {}
}
7. Save-and-pay redirect flow
Endpoint: GET /api/v1/checkout/public/sessions/{public_id}/savepay?t={token}&payment_method={method}
This endpoint is a convenience flow for payment methods where the shopper should first create a token and then continue directly with a payment. The backend redirects the shopper to the provider tokenization page, caches the intermediate state, and resumes the payment flow through /checkout/token/return/{public_id}?flow=savepay&k=....
This endpoint is not a JSON API endpoint. It performs a full-page redirect.
8. Return endpoints used by hosted checkout
GET /checkout/return/{public_id}
This endpoint is used after a payment attempt. It loads the latest transaction for the session, fetches pending transaction state when needed, syncs checkout session status, and then:
- redirects to merchant return_url on success
- redirects back to hosted checkout with ?result=cancelled, ?result=failed, or ?result=error when retry is possible
- renders an expiry page and then redirects to merchant return_url when the session is expired
GET /checkout/token/return/{public_id}
This endpoint is used after token creation. In save-and-pay mode, it resumes the payment flow automatically. If a second redirect is still required, the shopper is forwarded to the payment provider. Otherwise the shopper continues into the normal checkout return flow.
Session status lifecycle
- active - session exists and can still be used
- processing - transaction attempt exists and is pending
- success - latest transaction succeeded, session becomes terminal
- expired - session lifetime elapsed, session becomes terminal
Failed and cancelled payment attempts do not permanently close the session. The backend moves the session back to active so the shopper can retry, as long as the session has not expired yet.
Errors specific to checkout
- CHECKOUT_SESSION_NOT_FOUND - invalid public id or token, or unknown session
- CHECKOUT_SESSION_EXPIRED - session expired
- CHECKOUT_SESSION_NOT_PAYABLE - session no longer allows a payment attempt
- CHECKOUT_PAYMENT_METHOD_REQUIRED - missing payment method on pay request
- CHECKOUT_PAYMENT_METHOD_NOT_ALLOWED - method not in the checkout session allowlist
- CHECKOUT_PAYMENT_METHOD_NOT_ENABLED - method is not enabled for the merchant
- CHECKOUT_PAYMENT_TOKEN_REQUIRED - checkout session requires token usage
- CHECKOUT_TOKEN_REUSE_DISABLED - token payment attempted while token reuse is disabled
- CHECKOUT_PAYMENT_TOKEN_NOT_USABLE - token missing, cancelled, unconfirmed, or not part of the allowed token snapshot
- CHECKOUT_TOKEN_CREATION_DISABLED - token creation attempted while session is not configured for it
- CHECKOUT_TOKEN_CREATION_FAILED - provider token registration did not return a usable redirect URL