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
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer 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-Type | No (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-Key | No | Unique 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
| Field | Type | Required | Description |
|---|---|---|---|
amount | integer | Conditional | Amount in minor units (cents). 1499 = $14.99. Integer in the range 0–99,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. |
currency | string | Yes | Three-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. |
country | string | No | ISO 3166-1 alpha-2 country code, exactly 2 characters, uppercased (US, CA, GB) |
successUrl | string | No | HTTPS URL to redirect buyer after payment |
cancelUrl | string | No | HTTPS URL to redirect buyer on cancel |
mode | string | No | Payment 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. |
description | string | No | Human-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. |
locale | string | No | Checkout page language, max 10 chars (e.g. "en", "fr") |
expiresIn | integer | No | Session TTL in seconds (300–604800, default 1800). Minimum 5 minutes, maximum 7 days. |
buyerId | string | No | Your 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. |
buyerName | string | No | Buyer's name, max 200 chars (pre-fills billing form, encrypted at rest) |
buyerEmail | string | No | Buyer'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. |
lineItems | array | No | Order items to display on checkout page (max 100 items) |
metadata | object | No | Key-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
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Item name (1–200 chars) |
quantity | integer | Yes | Quantity (1-9999) |
unitAmount | integer | Yes | Unit price in minor units (integer ≥ 0) |
imageUrl | string | No | Product 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: abuyerIdthat 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 ifbuyerIdvaries. 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 theX-Request-Idfrom 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):
| Amount | Currency | Value |
|---|---|---|
1499 | USD | $14.99 |
1000 | EUR | 10.00 EUR |
999 | GBP | 9.99 GBP |
100000 | JPY | 100,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
amountis an integer in0–99,999,999; formode: "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.currencymust be exactly 3 characters (uppercased; not checked against an ISO 4217 allowlist)countrymust be exactly 2 characterssuccessUrlandcancelUrlmust be HTTPS (localhost exempt for test-mode keys only)expiresInmust be between 300 and 604800 (seconds — 5 minutes to 7 days)lineItemsmax 100 itemsmetadatavalues 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_fieldwith field suggestions.
Errors
| Status | Error | Cause |
|---|---|---|
| 400 | Validation error message | Invalid request body |
| 401 | Authentication required / Authentication failed | Missing or wrong Bearer token (codes auth_missing_bearer / auth_invalid_key) |
| 403 | Sandbox merchants cannot create live payment sessions | Sandbox key used to create a live session (auth_key_type_forbidden) |
| 422 | Idempotency-Key replay body does not match the original request. | Replayed Idempotency-Key with a changed body (idempotency_replay_incompatible) |
| 429 | Too many requests | Rate limited (rate_limit_exceeded; 10/min per IP on the sessions bucket) |
| 500 | Internal error | Server error (internal_error) |
| 501 | Card-on-file setup sessions are not yet available. | mode: "setup" — card-on-file setup sessions are not yet available (endpoint_not_implemented) |