Skip to main content
DocumentationFor developers

For developers

Connect a custom checkout

Create a hosted checkout from your server, retry safely and verify payment before fulfillment.

Reviewed October 11, 2026

Choose the hosted checkout API

This guide describes POST /api/v1/checkout/sessions. It creates a hosted checkout from line items and returns a payment URL. The separate /api/v1/sessions endpoint is a different session flow; its parameters and embed contract are not interchangeable.

Use Authorization: Bearer <merchant secret key> and Content-Type: application/json. Use the correct Sandbox or Live secret key for this mode-aware endpoint. Keep keys and webhook secrets on your server. Your server, not the browser, determines prices, discounts, tax and shipping.

Create the checkout

Save the returned id against your order before redirecting the customer to the returned url. Creation does not charge the customer. The following example is illustrative and requires your own server-side order and merchant credentials.

Example request · server side
curl https://arcanapayments.com/api/v1/checkout/sessions \
  -H "Authorization: Bearer $ARCANA_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order_1842_attempt_1" \
  -d '{
    "items": [{"name": "Order 1842", "qty": 1, "amountUsd": 49}],
    "reference": "order_1842",
    "successUrl": "https://your-store.com/order/complete",
    "cancelUrl": "https://your-store.com/cart",
    "collectShipping": false,
    "expiresInMinutes": 60
  }'

Request fields

  • items (required): 1–100 objects with name, qty and amountUsd. name is 1–200 characters; qty is an integer from 1 to 10,000; amountUsd is a USD unit price with at most two decimal places. Total must be $1–$999,999.99 and within merchant limits.
  • successUrl (required): an HTTPS return URL. cancelUrl is an optional HTTPS return-to-cart URL. Keep your own order-to-checkout mapping; never treat either redirect as proof of payment.
  • reference: your opaque order identifier, up to 100 characters.
  • customerEmail: optional buyer email. collectShipping defaults to true; use false if your store already collects it or the order does not require it.
  • expiresInMinutes: integer from 15 to 10,080; defaults to 1,440.
  • rails: optional subset of enabled methods, for example stripe_card, stripe_bank or usdc_wallet. Inspect rails_dropped for requested methods omitted from the created checkout.
  • customerReceipt: arcana (default) or merchant. merchant means your store owns customer receipt delivery; this choice is frozen when the checkout is created.
  • Only documented keys are accepted. Do not send card numbers, processor fields or a client-calculated total.

Retry without creating duplicate checkouts

Send an Idempotency-Key of 8–128 letters, numbers, dots, colons, underscores or hyphens for each saved order payment attempt. After a timeout, retry the same request body with the same key.

A new checkout returns HTTP 201. An exact replay returns HTTP 200 with Idempotency-Replayed: true. A changed request using an existing key returns a conflict. Investigate the saved attempt before deliberately creating a new one.

Receive and verify payment events

Configure a webhook URL for each environment in the merchant settings. Live events include invoice.processing (pending) and invoice.paid (ready to verify), with an invoice object. Sandbox sends checkout.session.completed or checkout.session.failed with a session object, mode: sandbox and live: false. Sandbox events never authorize a real order fulfillment.

The signature is HMAC-SHA256 over timestamp + '.' + the raw request body, using the environment's webhook signing secret. Headers include x-arcana-timestamp, x-arcana-signature (sha256=…) and x-arcana-event-id. Verify the original bytes before parsing; use constant-time comparison, a timestamp tolerance and event-ID checks.

Events can repeat or arrive out of order. Deduplicate eventId and the checkout being fulfilled in your database. Return 2xx only after durable processing or queuing succeeds. The authenticated developer guide includes the Node.js helper and complete verification example.

Confirm the order on your server

For real orders, retrieve GET /api/v1/checkout/sessions/{id} with the merchant's Live secret key. Require the saved Live checkout ID (inv_ followed by its UUID), kind: checkout and status: paid. Match reference, currency and the exact amount to your saved order, then fulfill once. Reject cs_test_ IDs, mode: sandbox and live: false even when status is paid. Current Live responses can omit mode and live; the downloadable checkoutPaysOrder helper validates the actual response contract. A paid checkout remains paid even when a separate refund record changes.

GET /api/v1/integration/status inspects the authenticated integration setup and is useful while Live collection is paused. A ready integration still requires successful end-to-end testing of your store's own order handling.

Test before switching Live

  • Use separate Sandbox credentials, webhook URL and signing secret.
  • Test successful, failed and pending outcomes; refreshes and repeated create requests; duplicate and delayed events; expired links; and mismatched order amounts.
  • Confirm which system sends receipts and how your store handles canceled or abandoned checkout.
  • A Sandbox status of paid is simulated. Reject it for real fulfillment; never use it as evidence that real processing or payout is enabled.
  • The authenticated developer reference is available at /admin/developers after sign-in.