Skip to content

Open a checkout session

POST/v1/sessions

Announces an order and returns the hosted checkout link to send the customer to. Called from your backend, never from the storefront.

Authentication — X-Client-Id and X-Api-Key.

The session starts pending and lapses after session_ttl_minutes unless it advances. A lapsed session cannot be transacted on, and it cannot be revived — open a fresh one.

The merchant's terms — the down payment and the reservation fee — are snapshotted onto the session as it opens, so an open quote never moves.

Not idempotent unless you make it so

Sending the same order_reference twice opens two sessions. Send an Idempotency-Key when you want a retry to be safe.

Idempotency

Pass an Idempotency-Key header — a UUID, one per checkout attempt — and a replay of the same request returns the original session instead of opening a second one. This is what makes a network timeout safe to retry: you get back the session you already created, even if you never saw the first response.

Same key, same body Returns the original session. Nothing new is created.
Same key, different body 409.
No key A new session every time, as above.

Keys are scoped to the merchant and mode that presented them, and expire after 24 hours — after that the same value is treated as new. Reuse one key across retries of a single attempt, and mint a new one for each new checkout.

A request that fails validation does not burn the key: fix the body and retry with the same one.

Request

Header Required Notes
Idempotency-Key No Makes a retry of this request safe. Max 255 characters; longer is a 400.
Field Type Required Notes
order_reference string Yes Your own identifier for the order. Max 100 characters.
total_amount integer Yes The order total in integer minor units (cents) — send 49999 for $499.99. Must sit inside the merchant's terms.
return_url string Yes Where the customer lands after completing checkout. HTTPS only, max 2048 characters.
cancel_url string Yes Where the customer lands if they back out. HTTPS only.
customer_email string or null No
customer_state string or null No The two-letter US state the order ships to, which the jurisdiction rules are applied against. Omit it and the merchant's own state is used instead, which is the more conservative reading.
metadata object or null No Your own string key/value pairs, echoed back on the session and on the contract it becomes. At most 50 keys; keys max 40 characters, values max 500. Values must be strings, and are trimmed — omit a key rather than sending an empty value.
curl -X POST "$SHOTPAY_URL/v1/sessions" \
  -H "X-Client-Id: $SHOTPAY_CLIENT_ID" \
  -H "X-Api-Key: $SHOTPAY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "order_reference": "ORD-1001",
    "total_amount": 49999,
    "customer_email": "customer@example.com",
    "customer_state": "TX",
    "return_url": "https://store.example/checkout/complete",
    "cancel_url": "https://store.example/cart",
    "metadata": { "order_id": "1001", "warehouse": "SEA-2" }
  }'
$session = Http::withHeaders([
    'X-Client-Id' => config('shotpay.client_id'),
    'X-Api-Key' => config('shotpay.api_key'),
    // One per checkout attempt, stored on the order so a retry reuses it.
    'Idempotency-Key' => $order->shotpay_idempotency_key,
])->post(config('shotpay.url').'/v1/sessions', [
    'order_reference' => $order->number,
    'total_amount' => $order->total_cents,
    'customer_email' => $order->email,
    'customer_state' => $order->shipping_state,
    'return_url' => route('checkout.complete'),
    'cancel_url' => route('cart'),
    'metadata' => ['order_id' => (string) $order->id, 'warehouse' => $order->warehouse_code],
])->json('data');

Response

201 Created — a Checkout Session.

{
  "data": {
    "id": "cs_9Xk2mQpR7vLzT4hB1nWsY6dF3jCgA8eU",
    "status": "pending",
    "order_reference": "ORD-1001",
    "total_amount": 49999,
    "down_payment_amount": 12500,
    "down_payment_percentage": 25,
    "checkout_url": "https://secure.shotpay.com/checkout/cs_9Xk2mQpR7vLzT4hB1nWsY6dF3jCgA8eU",
    "return_url": "https://store.example/checkout/complete",
    "cancel_url": "https://store.example/cart",
    "expires_at": "2026-08-12T15:30:00+00:00",
    "created_at": "2026-08-12T14:30:00+00:00",
    "metadata": { "order_id": "1001", "warehouse": "SEA-2" }
  }
}

Redirect the customer to checkout_url. Nothing is owed until they accept the plan there — see Checkout Flow.

Errors

Status When
400 The Idempotency-Key is longer than 255 characters.
401 The key is missing, unknown, revoked, or paired with a client id from the other mode.
403 With a reason: layaway is unavailable for this merchant or this customer's state. Without one: the merchant is not cleared to transact.
409 This Idempotency-Key was already used with a different body.
422 The request is wrong — most often a total outside the merchant's range.
429 Rate limited. Back off for Retry-After seconds.

See Error Handling for the reason values and the response bodies.