Skip to main content

Create a Checkout Session

Create a session on your server, then redirect the buyer to the checkout URL.

Endpoint

POST /v1/sessions
Authorization: Bearer vp_sk_live_xxx
Content-Type: application/json
Idempotency-Key: <unique-key> (optional, recommended)

Headers

HeaderRequiredDescription
AuthorizationYesBearer token (vp_sk_live_xxx or vp_sk_test_xxx). The header must use the Bearer scheme; a missing or malformed token returns 401.
Content-TypeNo (recommended)Send application/json. The request body is parsed as JSON regardless of this header — POST /v1/sessions does not reject a missing or mismatched Content-Type with a 415. Always send application/json so intermediaries and SDKs behave predictably; a body that isn't valid JSON fails to parse and surfaces as a 500.
Idempotency-KeyNoUnique key to prevent duplicate session creation. If you retry a request with the same key, the original session is returned instead of creating a new one. Recommended for all production integrations. See Idempotency below for retention behavior.

Request Body

FieldTypeRequiredDescription
amountintegerConditionalAmount in minor units (cents). 1499 = $14.99. Integer in the range 099,999,999. Required and must be ≥ 1 for mode: "payment" (the default). For mode: "setup" (card-on-file, no charge) it may be omitted and is normalized to 0.
currencystringYesThree-character currency code, uppercased server-side (USD, EUR, GBP). The field enforces exactly 3 characters; it does not validate ISO 4217 membership, so an unsupported but well-formed 3-letter code is accepted at this layer.
countrystringNoISO 3166-1 alpha-2 country code, exactly 2 characters, uppercased (US, CA, GB)
successUrlstringNoHTTPS URL to redirect buyer after payment
cancelUrlstringNoHTTPS URL to redirect buyer on cancel
modestringNoPayment mode — enum "payment" | "setup", default "payment". "payment" is a one-time charge and is the only mode generally available on hosted checkout. "setup" (collect + vault a card with no charge) is a not-yet-released capability — a mode: "setup" request currently returns endpoint_not_implemented (HTTP 501). There is no subscription / recurring mode on hosted checkout — for recurring flows, use Payment Intents directly.
descriptionstringNoHuman-readable description of the payment (max 500 chars). This is not the bank-statement descriptor — statement text is configured per-merchant server-side via a separate statement-descriptor field.
localestringNoCheckout page language, max 10 chars (e.g. "en", "fr")
expiresInintegerNoSession TTL in seconds (300–604800, default 1800). Minimum 5 minutes, maximum 7 days.
buyerIdstringNoYour stable, unique-per-user account ID (max 200 chars) — the same value every visit, not a per-visit token. See Buyer identification. Enables saved payment methods. For the embedded charge-and-save flow, a buyer on the session is the vaulting driver: with a buyer, submit() charges the card AND returns a reusable vp_pmt_* in one step; a guest / no-buyer session charges once and saves nothing (no token). See Tokenization — charge-and-save for the buyer-vaulting model.
buyerNamestringNoBuyer's name, max 200 chars (pre-fills billing form, encrypted at rest)
buyerEmailstringNoBuyer's email — must be a valid email, max 254 chars (encrypted at rest). When provided, Von Payments consolidates the buyer into a single email-keyed buyer record, linking the buyer across sessions. See Buyer identification.
lineItemsarrayNoOrder items to display on checkout page (max 100 items)
metadataobjectNoKey-value string pairs (each value max 500 chars). Stored on the session for your own buyer/app context (e.g. device_id, app_user_id). Note: session metadata is persisted on the session record but is not currently echoed into webhook event payloads.

Line Item Object

FieldTypeRequiredDescription
namestringYesItem name (1–200 chars)
quantityintegerYesQuantity (1-9999)
unitAmountintegerYesUnit price in minor units (integer ≥ 0)
imageUrlstringNoProduct image URL (HTTPS)

Buyer identification

Pass these fields on every session so Von Payments can link a buyer's transactions across sessions, consolidate them, and help you troubleshoot (duplicate charges, disputes, "is this the same buyer?").

  • buyerId — your stable, unique-per-user account ID. Send the same value every time this buyer pays, across visits, devices, and cards (e.g. user_8f3a2b — a persistent ID from your users table). Do not send a per-visit, per-page-load, session, cart, or random value: a buyerId that changes per visit makes a returning buyer look brand-new and breaks duplicate-charge detection.
  • buyerEmail — the buyer's email. When provided, Von Payments keeps one buyer record keyed by email, linking the buyer across sessions even if buyerId varies. This is the most reliable cross-session identifier.
  • metadata — optional extra context as string key/values, e.g. { "app_user_id": "8f3a2b", "device_id": "d-1029" }.

Minimum for clean linking: send buyerId (stable) and buyerEmail together. If you're generating this call from an SDK or an AI agent, source buyerId from the authenticated user's persistent record ID — never a request-scoped or random token.

Contacting support about a transaction? Include the buyerId, the buyer's email, and the X-Request-Id from any API response — these let support correlate the transaction to the buyer instantly.

Response

{
"id": "vp_cs_live_k7x9m2n4p3q8r5t1",
"checkoutUrl": "https://checkout.vonpay.com/checkout?session=vp_cs_live_k7x9m2n4p3q8r5t1",
"expiresAt": "2026-03-31T15:30:00.000Z",
"integrationMode": "embed"
}

The POST /v1/sessions response returns the session id, the checkoutUrl to redirect the buyer to, the session expiry (expiresAt), and integrationMode (the effective integration mode for the embedded flow — "embed" or "elements"). Processor selection (Stripe, Adyen, etc.) happens server-side inside Von Payments and is not exposed on the merchant API. See VORA — Payment Routing for why.

The session id has the form vp_cs_{live|test}_{16-character id} — for example vp_cs_live_k7x9m2n4p3q8r5t1.

The checkoutUrl host depends on the session. Test-mode sessions (and live-mode sessions without a configured merchant slug) use the platform host https://checkout.vonpay.com/checkout?session=<sessionId>. Live-mode sessions with a merchant slug use the merchant subdomain https://<slug>.vonpay.com/checkout?session=<sessionId>. Always redirect the buyer to the exact checkoutUrl returned in the response rather than constructing it yourself.

Session Expiry

Sessions expire after the expiresIn window (default 30 minutes, up to a max of 7 days). If the buyer hasn't completed payment by then, the session status becomes expired and the checkout page shows an error.

Idempotency

Idempotency-Key is bound to the resulting checkout-session record, not to a separate cache with a TTL. A replayed key returns the same session for as long as the session record is retained on our side — which today is effectively the lifetime of the row. Use unique keys per attempt (e.g. order_123_attempt_1) so a retry with a different request body is surfaced as idempotency_replay_incompatible (HTTP 422) rather than silently merged.

successUrl / cancelUrl

successUrl and cancelUrl are validated for shape only — HTTPS scheme required (with localhost exempt for test-mode keys), max 2048 characters. Live-mode keys reject localhost/loopback redirect URLs. There is no dashboard allowlist of buyer-redirect URLs today. Any HTTPS URL is accepted on session create; for security-sensitive flows, terminate the redirect at a server-side endpoint you control and verify the return signature before trusting the buyer's browser state. See Handle the return for the signature-verification flow.

Amount Format

Amounts are always in minor units (the smallest currency unit):

AmountCurrencyValue
1499USD$14.99
1000EUR10.00 EUR
999GBP9.99 GBP
100000JPY100,000 JPY (JPY has no minor unit)

Example: With Line Items

curl -X POST https://checkout.vonpay.com/v1/sessions \
-H "Authorization: Bearer vp_sk_live_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order_456_attempt_1" \
-d '{
"amount": 3298,
"currency": "USD",
"country": "US",
"successUrl": "https://mystore.com/order/456/confirm",
"cancelUrl": "https://mystore.com/cart",
"description": "Order #456",
"locale": "en",
"buyerId": "user_8f3a2b",
"buyerName": "Jane Doe",
"buyerEmail": "jane@example.com",
"lineItems": [
{ "name": "Wireless Headphones", "quantity": 1, "unitAmount": 2499 },
{ "name": "USB-C Cable", "quantity": 1, "unitAmount": 799 }
],
"metadata": { "orderId": "order_456" }
}'

Example: Simple Payment (No Items)

curl -X POST https://checkout.vonpay.com/v1/sessions \
-H "Authorization: Bearer vp_sk_live_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: simple_pay_001" \
-d '{
"amount": 5000,
"currency": "USD",
"country": "US",
"successUrl": "https://mystore.com/thank-you"
}'

If no lineItems are provided, the checkout page shows the total amount without an itemized breakdown.

Validation Rules

  • amount is an integer in 099,999,999; for mode: "payment" (the default) it is required and must be ≥ 1, so payment-mode amounts are positive integers from 1 to 99,999,999. mode: "setup" may omit it.
  • currency must be exactly 3 characters (uppercased; not checked against an ISO 4217 allowlist)
  • country must be exactly 2 characters
  • successUrl and cancelUrl must be HTTPS (localhost exempt for test-mode keys only)
  • expiresIn must be between 300 and 604800 (seconds — 5 minutes to 7 days)
  • lineItems max 100 items
  • metadata values must be strings, max 500 characters each
  • Unknown / unrecognized fields are rejected (strict schema). Fields are camelCase; a snake_case key returns 400 validation_unknown_field with field suggestions.

Errors

StatusErrorCause
400Validation error messageInvalid request body
401Authentication required / Authentication failedMissing or wrong Bearer token (codes auth_missing_bearer / auth_invalid_key)
403Sandbox merchants cannot create live payment sessionsSandbox key used to create a live session (auth_key_type_forbidden)
422Idempotency-Key replay body does not match the original request.Replayed Idempotency-Key with a changed body (idempotency_replay_incompatible)
429Too many requestsRate limited (rate_limit_exceeded; 10/min per IP on the sessions bucket)
500Internal errorServer error (internal_error)
501Card-on-file setup sessions are not yet available.mode: "setup" — card-on-file setup sessions are not yet available (endpoint_not_implemented)