Skip to main content
DevelopersAPI v1

Your store.
Arcana checkout.

Create a payment session from your backend. Give customers an Arcana checkout. Bring the confirmed payment back to your order system.

  1. 01Your serverCreates the checkout
  2. 02Arcana PaymentsCollects the payment
  3. 03Your storeVerifies and fulfills

A small integration.
A complete payment flow.

Use the API from a custom storefront, commerce backend or server extension. Your store owns the cart, pricing and fulfillment. Arcana manages the payment page and payment status.

Before your first request

  • An Arcana merchant with approved payment methods. Live collection must be enabled before taking real payments.
  • The mode’s secret API key and webhook signing secret, saved in your server environment.
  • A public HTTPS endpoint for payment events, plus your store’s return URLs.

In your merchant workspace, open Settings and choose Sandbox or Live. Each has its own API key, signing secret, webhook URL and payment preferences. API keys are displayed once and cannot be retrieved later. Store secrets on your server. Replacing a key immediately invalidates the previous key for that mode.

Create a sessioncURL
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
  }'

Run this from your server or terminal with your merchant’s key. Creating a checkout does not charge a customer.

Live response · selected fieldsJSON
{
  "id": "inv_3f9c1e2a-7b4d-4c8e-a915-0d62b8e4f7a1",
  "kind": "checkout",
  "status": "open",
  "reference": "order_1842",
  "amountUsd": 49,
  "currency": "USD",
  "url": "https://arcanapayments.com/pay/UNGUESSABLE_TOKEN",
  "expiresAt": "2026-10-08T01:00:00.000Z",
  "expired": false
}

Turn a saved order into a checkout.

Calculate the final total on your server, including discounts, tax and shipping. Save the returned id against your order before redirecting the customer to url.

Use the downloadable helperNode.js 20+
import { createArcanaClient } from "./arcana-payments.mjs";

const arcana = createArcanaClient({
  secretKey: process.env.ARCANA_SECRET_KEY,
});

// order comes from your authenticated server-side cart.
// Calculate products, discounts, tax and shipping in your store.
const checkout = await arcana.createCheckout({
  items: [{ name: "Order " + order.number,
    qty: 1, amountUsd: order.totalCents / 100 }],
  reference: order.publicId,
  collectShipping: false,
  successUrl: "https://your-store.com/order/complete",
  cancelUrl: "https://your-store.com/cart",
}, {
  idempotencyKey: order.paymentAttemptKey,
});

// Save checkout.id on the order, then redirect to checkout.url.
// Retrying this exact request uses the same paymentAttemptKey.
Keep your integration private.

The example sends a generic order label. Arcana sends an opaque reference and payment amount to Stripe instead of cart details. The processor still collects the information it needs from the payer inside its secure payment fields.

You can send individual line items when you want them shown at checkout. If your store already collects shipping, use collectShipping: false to avoid asking twice.

One payment attempt. One key.

Send a stable Idempotency-Key for each order payment attempt. If a request times out, retry the same body with the same key. Arcana returns the checkout already created.

201 New checkout
First successful creation. Save the returned checkout ID.
200 Request replayed
Same merchant, key and normalized order details. The header Idempotency-Replayed: true identifies a retry.
409 Different details
idempotency_conflict means the key already belongs to another request. Review the existing order before starting a new attempt.

Keys use 8–128 letters, numbers, dots, colons, underscores or hyphens. They remain associated with the saved checkout, including after payment or expiry, for as long as the record is retained. Sandbox records expire from storage after 30 days. Changing a cart requires a deliberate new attempt; never rotate a key just because the network failed.

Let the payment come to you.

Set a webhook URL for each mode in the merchant settings. The API below configures Live webhooks. Arcana signs each event using that mode’s signing secret and retries failed deliveries.

Configure the webhook destinationcURL
curl -X PATCH https://arcanapayments.com/api/v1/merchant \
  -H "Authorization: Bearer $ARCANA_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{"webhookUrl":"https://your-store.com/api/arcana/webhook"}'
invoice.paidReady to verify

The event includes eventId and invoice with the checkout ID, your reference, amount, customer, shipping and payment details. An invoice.processing event means payment is still pending.

Live · verify before fulfillmentNode.js
import { verifyArcanaWebhook, checkoutPaysOrder }
  from "./arcana-payments.mjs";

// In your server's POST handler, before any JSON body parser:
const raw = Buffer.from(await request.arrayBuffer());
const event = verifyArcanaWebhook(
  raw, request.headers, process.env.ARCANA_WEBHOOK_SECRET,
);

if (event.type === "invoice.paid" &&
    event.invoice?.kind === "checkout") {
  // Find the order using your saved Arcana checkout ID.
  const order = await yourOrders.byCheckoutId(event.invoice.id);
  const checkout = await arcana.retrieveCheckout(event.invoice.id);
  if (!order || !checkoutPaysOrder(checkout, {
    checkoutId: order.arcanaCheckoutId,
    reference: order.publicId,
    amountCents: order.totalCents,
  })) throw new Error("Payment does not match this order");

  // Your database transaction should deduplicate event.eventId AND
  // checkout.id, mark the order paid, and queue fulfillment once.
  await yourOrders.recordVerifiedPaymentOnce(order, event, checkout);
}
// Return 2xx only after the durable transaction/queue commits.
// Return an error if verification or durable processing fails.

yourOrders represents your own database integration. Connect the transaction and fulfillment queue before using this outline in production.

Signature format and delivery behavior

The signature is HMAC-SHA256 over timestamp + "." + rawBody, using your webhook secret. Headers: x-arcana-timestamp, x-arcana-signature: sha256=…, and x-arcana-event-id.

The helper checks the original bytes, uses a constant-time signature comparison and enforces a five-minute timestamp window. It also checks that the event ID in the header matches the signed body.

Events may be delivered more than once or arrive out of order. Arcana makes up to 12 delivery attempts with backoff; failed events are visible in admin and can be resent. Your database must deduplicate both the event and the checkout being fulfilled.

Confirm the payment on your server.

The buyer returns to successUrl with checkout_id. Use your saved order-to-checkout mapping and the authenticated status endpoint. The redirect alone does not prove payment.

Retrieve checkout statuscURL
curl https://arcanapayments.com/api/v1/checkout/sessions/inv_YOUR_ID \
  -H "Authorization: Bearer $ARCANA_SECRET_KEY"
Payment statuses and fulfillment behavior
StatusWhat your store should do
openAwait payment. When expired is true, the link cannot start another payment.
processingShow a pending state. Wait for confirmation before fulfillment.
paidVerify checkout ID, order reference, USD currency and the exact total; then fulfill once.
voidDo not fulfill. A void record is not a refund receipt.

A payment already in flight can complete after the checkout expires. Continue processing verified events for the saved checkout. Treat refunds as a separate workflow with the merchant’s payment method.

The fields your store sends.

POST/api/v1/checkout/sessions

Use Authorization: Bearer <secret key> and Content-Type: application/json. Requests are limited to 64 KiB. Unknown fields and invalid value types are rejected.

itemsrequired
1–100 items. Each has a name (1–200 characters), integer qty (1–10,000), and numeric amountUsd unit price with at most two decimal places. Total: $1–$999,999.99, within merchant limits.
successUrlrequired
HTTPS page to return to after payment. Arcana adds checkout_id. Verify the payment on your server before showing an order as paid.
cancelUrloptional
HTTPS link back to your cart. Returning here does not cancel or refund a payment.
referencerecommended
Your opaque order identifier, up to 100 characters. Keep sensitive customer and product information out of identifiers and URLs.
collectShippingoptional
Defaults to true. Set false for digital products, services, or when your store already has the shipping address.
customerEmailoptional
Prefills the buyer’s email. Leave it out to collect it at checkout.
expiresInMinutesoptional
An integer from 15 to 10,080. Defaults to 1,440 (24 hours). An expired link cannot start a new payment.
railsoptional
A subset of your merchant’s enabled methods, such as stripe_card, stripe_bank, usdc_wallet, or zelle. Omit it to use the merchant configuration. Check rails_dropped in the response.
Handle API errors

Errors return { "error": "error_code" }. 400: invalid fields or key. 401: invalid merchant credentials. 404: unknown checkout or a different merchant’s checkout. 409: request conflict, paused merchant, or unavailable payment configuration. 413: request too large. 415: JSON content type required. 429: rate limit reached. 503: temporarily unavailable.

Each merchant can make up to 120 checkout creation requests and 600 status requests per minute, separately for Sandbox and Live. Integration status shares the status allowance. These limits include retries and are shared across credentials within the same mode. On 429, wait for the number of seconds in Retry-After (the helper exposes error.retryAfterSeconds), then retry with the original idempotency key and body.

For an uncertain creation response or temporary failure, retry with the same idempotency key and request body. Validate errors before retrying; avoid logging credentials, customer details or payment URLs.

Test the full order journey.

Issue a sk_test_… key in your merchant workspace and use the same checkout endpoints. Sandbox saves a separate test checkout and opens an Arcana page where you can simulate success or decline. It exercises the API, return flow and signed webhooks without collecting money or submitting a card-network transaction. Sandbox can be used while Live collection is paused.

Sandbox responses have mode: "sandbox", live: false and IDs beginning with cs_test_. Status is open, paid, failed or expired. Even a paid Sandbox result is simulated and must never fulfill a real order.

Configure the Sandbox webhook URL and use its separate whsec_test_… signing secret. Sandbox events are checkout.session.completed or checkout.session.failed, with an evt_test_… event ID and a session object containing the checkout status, reference and exact amount. Both the event and session carry the Sandbox mode flags. The downloadable helper checks the event’s mode; its checkoutPaysOrder helper always refuses Sandbox results.

Send a synthetic order reference and generic item label. Sandbox retains only the reference, total, return URLs, status and delivery state for 30 days; it does not save customer details or item descriptions. Use GET /api/v1/integration/status to check the authenticated merchant, mode and recent activity. The public demo is a visual preview.

  • Repeated create requests return the same checkout.
  • Modified totals or order references are refused on a reused key.
  • Bad signatures, stale events and duplicate deliveries cannot fulfill an order.
  • Pending payments stay pending; confirmed payments match your saved total.
  • Expired checkouts and return-page refreshes do not create extra orders.

The API works with storefronts that let you add a server integration. Marketplace payment-provider listings and native platform plugins require their own setup; this guide does not install a gateway into a hosted platform.