DocsAPI ReferenceCheckout Sessions
Checkout Sessions API
A Stripe-style HTTP API for accepting payments from your own apps and websites. Create a session on your server, let the customer pay on the hosted page or embedded in your frontend, then verify before fulfilling.
Basics
| Base URL | https://saleonix.com/api/v1 |
| Authentication | Bearer key in the Authorization header (or X-Api-Key). Keys in query strings are rejected. |
| Currency | EUR only — there is no currency parameter; sending one returns currency_unsupported. |
| Units | All amounts are integer cents: 4318 = €43.18. Floats and numeric strings are rejected. |
| Format | JSON bodies with Content-Type: application/json (required on POST; missing or wrong type returns 415 unsupported_media_type). |
Two integration styles
| Style | Flow | Best for |
|---|---|---|
| Hosted checkout (recommended) | Your server creates a session with your secret key and redirects the customer to the returned url. Saleonix renders the payment page. | Fastest integration, minimal PCI scope |
| Embedded checkout | Your server creates the session and hands client_secret to your frontend, which confirms with your publishable key. | Custom payment UX in your app — easiest with the React SDK |
Create a checkout session
/api/v1/checkout/sessionsSecret keyA session charges in exactly one of two modes:
- Price mode — pass
price_id(+ optionalquantity); the amount is read from your catalog on the server. - Amount mode — pass
amount(+ requireddescription); your server states the total directly. Built for carts, shipping, fees and discounts.
Passing both (or neither) of price_id / amount is rejected. In bothmodes the customer's browser never carries an amount — only your server decides what is charged.
| Field | Type | Required | Description |
|---|---|---|---|
| price_id | string | price mode | A price in your store (find it on the product page in the dashboard). A price_ prefix is tolerated. Must be active, one-time and EUR — recurring prices return price_mode_unsupported. |
| quantity | integer | no | 1–100, default 1. Price mode only — rejected in amount mode. |
| amount | integer | amount mode | Total in cents. Minimum 50 (€0.50), maximum €10,000 by default. |
| description | string | amount mode | 1–255 chars, plain text. Shown to the customer on the hosted checkout page. |
| success_url | string | no | Absolute URL; the customer is redirected here after payment with ?session_id=cs_... appended. HTTPS required in live mode. |
| cancel_url | string | no | Stored for client-side use. Validated the same way as success_url (absolute URL, HTTPS required in live mode). |
| customer_email | string | no | Prefills the customer record. |
| metadata | object | no | Up to 20 keys (1–40 chars each). Values may be strings, numbers or booleans (coerced to strings, ≤500 chars each). Echoed back on reads. |
| expires_at | integer | no | Epoch seconds. Clamped to 30 min – 24 h from now; default 24 h. |
Price mode
Create session — price mode
curl -X POST https://saleonix.com/api/v1/checkout/sessions \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-12345" \
-d '{
"price_id": "clpr1ce...",
"quantity": 1,
"success_url": "https://example.com/thanks",
"customer_email": "buyer@example.com",
"metadata": { "internal_ref": "12345" }
}'Amount mode
Create session — amount mode
# A cart of 3 items + shipping − coupon = €43.18 → amount: 4318 (cents)
curl -X POST https://saleonix.com/api/v1/checkout/sessions \
-H "Authorization: Bearer sk_test_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: wc-order-1066" \
-d '{
"amount": 4318,
"description": "Order #1066 — example-shop.com (3 items incl. shipping)",
"success_url": "https://shop.example/thanks",
"customer_email": "buyer@example.com",
"metadata": { "order_id": "1066" }
}'Response 201
checkout.session
{
"id": "cs_test_aBcD...",
"object": "checkout.session",
"url": "https://saleonix.com/checkout?sid=...&exp=...&sig=...",
"client_secret": "cs_test_aBcD..._secret_XyZ...",
"status": "open",
"payment_status": "unpaid",
"livemode": false,
"currency": "eur",
"amount_subtotal": 10000,
"amount_tax": 1800,
"amount_total": 11800,
"quantity": 1,
"price_id": "clpr1ce...",
"product_id": "clxyz...",
"description": null,
"order_id": "clabc...",
"customer_email": "buyer@example.com",
"success_url": "https://example.com/thanks",
"cancel_url": null,
"metadata": { "internal_ref": "12345" },
"created": 1780000000,
"expires_at": 1780086400
}url— the hosted checkout page (a signed link, valid until the session expires).client_secret— returned only here. Store it server-side; pass it to your frontend only for embedded checkout. Treat it like a password for this one payment.
Idempotency
Idempotency-Key header with a unique value per order (max 191 characters). Retries with the same key replay the original session (including its client_secret) with HTTP 200 instead of creating and charging twice. Reusing a key with a different body returns 409 idempotency_key_reused.Retrieve a session
/api/v1/checkout/sessions/{id}Secret keySame shape as create, with client_secret: null and url: null once the session is no longer open (paid or expired). Fulfill the order when status is "complete" and payment_status is "paid". If you created the session in amount mode, also check that amount_total equals the total you expect before fulfilling.
Retrieve & verify
curl https://saleonix.com/api/v1/checkout/sessions/cs_test_aBcD... \
-H "Authorization: Bearer sk_test_xxx"| status | meaning |
|---|---|
open | awaiting payment |
complete | payment succeeded |
expired | deadline passed; create a new session |
| payment_status | meaning |
|---|---|
unpaid | no successful attempt yet |
paid | settled |
failed | an attempt was declined — the session cannot be retried; create a new one |
Confirm a session (embedded checkout)
/api/v1/checkout/sessions/{id}/confirmPublishable key + client_secretCall this from the customer's browser. Both credentials are required: the publishable key identifies your store; the client_secret proves this specific session was created by your server.
| Field | Type | Required | Description |
|---|---|---|---|
| client_secret | string | yes | The value returned when the session was created. |
| card | object | yes | number, exp_month, exp_year, cvc. |
| customer_email | string | no | Used for the customer record; falls back to the email from create. |
| customer_name | string | no | Stored on the customer record when an email is provided. |
Confirm with card details
// Runs in the customer's browser with your PUBLISHABLE key.
// clientSecret was injected by your server after creating the session.
const res = await fetch(
`https://saleonix.com/api/v1/checkout/sessions/${sessionId}/confirm`,
{
method: "POST",
headers: {
Authorization: "Bearer pk_test_xxx",
"Content-Type": "application/json",
},
body: JSON.stringify({
client_secret: clientSecret,
card: {
number: "4242424242424242",
exp_month: 12,
exp_year: 2030,
cvc: "123",
},
customer_email: "buyer@example.com",
}),
}
);
const data = await res.json();
if (res.ok) {
window.location.href = data.redirect_url;
} else {
showError(data.error.message);
}Success 200
Response
{
"id": "cs_test_aBcD...",
"object": "checkout.session",
"status": "complete",
"payment_status": "paid",
"order_id": "clabc...",
"redirect_url": "https://example.com/thanks?session_id=cs_test_aBcD..."
}Test cards (test mode only)
| Card | Result |
|---|---|
4242 4242 4242 4242 | Approved |
4343 4343 4343 4343 | Declined (terminal for session) |
Any other number returns 400 unsupported_test_card. The session stays payable, but the attempt still counts toward the per-session limit.
Failure semantics
- Invalid card input (
invalid_card_number,invalid_expiry,expired_card,invalid_cvc) returns HTTP 402 withtype: "card_error"and is rejected before any charge attempt — the session stays payable. - A gateway decline (
card_declined, also HTTP 402) is terminal for the session: create a new session to retry. This is deliberate anti-card-testing behavior. - Each confirm call counts toward a per-session attempt budget (default 5), including validation failures and wrong
client_secret; beyond that it returns429 too_many_attempts. - Rare gateway or settlement failures may return HTTP 500 with
payment_outcome_unknownorsettlement_failed— do not retry the payment; contact support.
Building embedded checkout by hand?
Errors
All errors share one envelope:
Error shape
{ "error": { "type": "card_error", "code": "card_declined", "message": "..." } }| HTTP | type | Typical codes |
|---|---|---|
| 400 | invalid_request_error | parameter_invalid, invalid_json, session_expired, invalid_success_url, invalid_cancel_url, unsupported_test_card, price_inactive, price_mode_unsupported, currency_unsupported, metadata_invalid, idempotency_key_invalid |
| 401 | authentication_error | missing_api_key, invalid_api_key, wrong_key_type, invalid_client_secret, environment_mismatch |
| 402 | card_error | card_declined, invalid_card_number, invalid_expiry, expired_card, invalid_cvc |
| 404 | invalid_request_error | resource_missing |
| 409 | invalid_request_error | session_already_complete, session_payment_failed, order_already_processed, idempotency_key_reused |
| 415 | invalid_request_error | unsupported_media_type |
| 429 | rate_limit_error | rate_limited, too_many_attempts (honor Retry-After) |
| 500 | api_error | internal_error, payment_outcome_unknown, settlement_failed |
Security model
- No browser-facing request ever carries an amount: price mode derives amounts from the catalog; amount mode accepts them only on the secret-key endpoint. The confirm endpoint ignores injected amount fields.
- Currency is never client-settable — EUR is pinned server-side.
- Keys are verified by SHA-256 hash; raw keys are never stored or logged.
client_secretis hashed at rest and returned exactly once.- Concurrent confirms cannot double-charge: the order is claimed atomically before the gateway is called.
- Card data is forwarded to the payment gateway only — never persisted or logged.
- TEST and LIVE are fully isolated: keys, sessions, and money never cross.
- Per-IP, per-key and per-session rate limits; all auth failures are audited.