Session Object
A checkout session represents a single payment attempt from creation to completion.
Session ID
Format: vp_cs_{env}_{nanoid}
vp_cs_test_k7x9m2n4p3q8r1s5: sandbox sessionvp_cs_live_k7x9m2n4p3q8r1s5: production session
The ID ends in a 16-character random string. It cannot be guessed.
Fields
Core fields
| Field | Type | Always Present | Description |
|---|---|---|---|
id | string | Yes | Session ID |
status | string: pending | processing | succeeded | failed | expired | Yes | Where the session is in its lifecycle. The status table below gives the trigger for each value. |
expiredFrom | string | null: pending | processing | failed | No | How an expired session expired, and whether a payment may still exist on it. null whenever status is not expired. pending means no payment had been started through Von Payments' servers, so nothing was charged. failed means nothing was charged either: a declined elements + chargeAtSubmit session that was closed with nothing recorded, or a session whose time limit passed while the buyer was at their bank's security check, once the payment provider confirmed that payment was never authorized. processing means a payment HAD been started and its outcome was never reported before the time limit passed: do not read it as "nothing was charged". On an elements + chargeAtSubmit session it can later change to failed when the provider confirms no authorization after the bank check times out; that attempt's payment_intent.failed / charge.failed webhooks announce it. Otherwise do NOT rely on polling the session or on a webhook to settle it, because a payment that lands on an already-expired session is not reported today. Contact support with the session id before charging the buyer again for the same order. null on a session that IS expired is unknown, and should be treated like processing. |
paymentStatus | string: unpaid | held | paid | cancelled | Yes | Did the money move? Deliberately separate from status, which says whether the buyer finished. unpaid means nothing is committed. held means authorised, not captured: the card is on hold and you must capture or void it. Do not fulfil on held. An authorisation you never capture lapses at the issuer and the sale is lost, and the window is shorter than most integrations assume: around seven days on Mastercard, American Express and Discover, about five on a Visa merchant-initiated payment, as little as two for card-present. Treat the shortest as your deadline unless you know the brand. paid means captured, and is the one to fulfil on. cancelled means a hold released without ever being captured. Refunds are not reported here: a captured-then-refunded session stays paid, so read the refund for that. |
mode | string: payment | setup | Yes | Payment mode. |
captureMethod | string: automatic | manual | Yes | Echoes the captureMethod the session was created with (automatic or manual), so you can see which semantics it carries without inferring them from paymentStatus. |
integrationMode | string: embed | elements | Yes | The integration mode the session was created with, as applied: embed if you asked for elements and your account does not support it. The same value the create response returned. |
channel | string | null: virtual_terminal | Yes | Which Von Payments product created the session: virtual_terminal for a sale keyed in your dashboard's virtual terminal, null for a session your integration created. Set by Von Payments; you cannot send it. |
chargeAtSubmit | boolean | Yes | Whether the embedded fields charge the buyer on submit, as applied at create: false unless the session is an elements session in payment mode that asked for it. |
threeDSecure | object | Yes | How the session will authenticate the cardholder if the bank asks for it: an object whose mode is in_page, redirect or not_applicable (treat it as an open list). Worked out each time you read it, so it follows your account's current setup. It says what will happen, not that a challenge ran. |
storedCredentialUse | string | null: recurring | installment | unscheduled | Yes | The storedCredentialUse declaration recorded at create: recurring, installment or unscheduled. null means none was declared. Read it back to confirm your declaration landed. |
setupForFutureUse | string | null: on_session | Yes | on_session when you declared setupForFutureUse at create, otherwise null. This is your declaration. What a saved card actually records is on the charge result, where a buyer who ticked "save my card" (off_session) wins. |
merchantId | string | Yes | Merchant account that owns this session |
amount | integer | Yes | Payment amount in minor units |
currency | string | Yes | ISO 4217 currency code (a required 3-letter uppercase code) |
country | string | null | No | ISO 3166-1 alpha-2 country code (optional) |
description | string | null | No | Human-readable description of the payment (max 500 characters) |
locale | string | null | Yes | The checkout page language you sent at create, as stored. null when none was sent. |
successUrl | string | null | No | Redirect URL on success. Echoes the value supplied at creation (null if none) |
cancelUrl | string | null | No | Redirect URL on cancel. Echoes the value supplied at creation (null if none) |
shipping | string: none | auto | required | Yes | Shipping-address collection mode actually applied: none, auto or required. Not necessarily what you sent: omit the field on create and this is your account setting (none out of the box); send it with a publishable key and a value weaker than your account setting is raised to it. See Asking the buyer for more than a card. |
billingAddress | string: none | auto | required | Yes | Billing-address collection mode actually applied: none, auto or required. Your account setting when you omitted it. This is the mode, not the buyer's address. |
phone | string: none | auto | required | Yes | Phone collection mode actually applied: none, auto or required. The number the buyer typed is buyerPhone. |
collectEmail | boolean | Yes | Whether the checkout shows an email input even when a buyer email was pre-filled, as stored at create. |
nameCollection | string: none | auto | required | Yes | Whether this session requires the buyer's name: none, auto or required, from your account's buyer-field settings when the session was created. |
emailCollection | string: auto | required | Yes | Whether this session requires the buyer's email: auto or required, from your account's buyer-field settings when the session was created. Email is always collected, so it is never none. |
shippingAddress | object | null | No | Buyer's shipping address (only present when status is succeeded) |
buyerId | string (≤ 200) | null | Yes | Your own reference for this session's buyer, as sent at create. null when the session has none. The buyer's email and name are personal data and are never returned here. |
buyerPhone | string | null | Yes | The phone number the buyer typed on the checkout page. null when none was collected. Not the same as phone, the collection mode you set at creation, which decides whether the field is shown. |
transactionId | string | null | No | The payment provider's reference for this session's payment (set on completion). Not the id refunds take: use vp_tx_id. |
vp_tx_id | string | null | Yes | The Von transaction id (vp_tx_*): the id POST /v1/refunds takes as transaction, and the same vp_tx_id the charge.* webhooks carry, so a server that confirms a payment by reading the session can refund it without a webhook. null until captured, and while paymentStatus is held. Can trail paid by a few moments: read again rather than treating null as "no payment". Also null when vp_tx_count is 2 or more. |
vp_tx_count | integer | Yes | How many separate payments are recorded for this session: 0 before payment and while held, 1 normally. 2 or more means the buyer was charged more than once: vp_tx_id is then null on purpose. Check the payments by hand before refunding or fulfilling. |
amount_refunded | integer | null | Yes | Total refunded so far on the session's payment, in minor units: refunds done or still in progress (requested). Failed and canceled refunds are not counted. On GET only; PATCH and expire return null. Also null when vp_tx_id is null or the totals could not be read at that moment. To list the refunds themselves, call GET /v1/refunds?transaction= with vp_tx_id. |
remaining_refundable | integer | null | Yes | What a refund with no amount would return now, in minor units: the captured amount minus amount_refunded. A refund still in progress is already subtracted; if it later fails, its amount comes back. 0 whenever refundable is false. null in the same cases as amount_refunded. |
refundable | boolean | null | Yes | Whether the payment qualifies for a refund now, by the same rules POST /v1/refunds applies: false when it is not settled yet, disputed, already fully refunded or not refundable through the API. Account conditions, such as refunds being switched on for your payment provider, are still checked by the refund call. null in the same cases as amount_refunded. |
paymentIntentId | string | null | Yes | The payment intent this session's payment created. On a captureMethod: "manual" session this is the id you capture or void once paymentStatus reads held. Also set on a paid automatic payment, where the money is already taken: use it to read or refund, never to capture. Decide from paymentStatus (only held owes a capture), not from this field being present. null before payment, for a few moments while a payment is confirmed, and when more than one payment intent is recorded (check by hand rather than capturing either). |
retry | object | null | Yes | Whether a declined elements + chargeAtSubmit session will take another card, on an account with retry on the same session switched on: { allowed, attempts_remaining }. allowed: true means a decline re-opened the session (status is pending) and the buyer can pay with another card. allowed: false with attempts_remaining above 0 means re-opened, with the next card form still being prepared: read again in a second. allowed: false with attempts_remaining: 0 means the session ended with a decline (failed). null when retry on the same session is not switched on for your account, the session is not elements + chargeAtSubmit, nothing has been declined, or a decline handed the session back (pending) without using up an attempt, in which case it can still be paid. Only allowed: false with attempts_remaining: 0 is final; close the session before you cancel an order on it. This is the only place to learn it after a decline on the bank's 3-D Secure page, where no charge response is waiting. The failure webhooks carry the same field. |
last_payment_error | object | null | Yes | The most recent declined attempt, when the session failed, was re-opened after a decline, or is expired with expiredFrom: "failed": { code, decline_code }. code is card_declined (the bank refused) or payment_failed (any other reason); decline_code uses the same values as a payment intent's decline_code, or null. Filled in on GET only; PATCH and expire return null. Also null if it could not be read at that moment, which never fails the read. |
metadata | object of string | null | No | Merchant-provided key-value pairs (passed through to webhooks) |
createdAt | string | Yes | ISO 8601 creation timestamp |
updatedAt | string | Yes | ISO 8601 last update timestamp |
expiresAt | string | Yes | ISO 8601 expiry timestamp |
A buyer on the session is what makes the embedded charge-and-save flow vault a reusable vp_pmt_* alongside the charge; a guest session charges once and vaults nothing (charged: true, no token). A returning buyer also sees the cards saved in prior completed sessions inside the embed — buyer identification.
Payment routing (VORA)
Processor selection happens server-side. Neither POST /v1/sessions nor GET /v1/sessions/:id tells you which processor runs the payment — VORA.
Status Lifecycle
pending ──> processing ──> succeeded
└──> failed ──> succeeded (retry converges)
pending ──> expired
processing ──> expired
| Status | Description | Trigger |
|---|---|---|
pending | Session created, waiting for buyer | POST /v1/sessions |
processing | Buyer has acted; charge in flight | Buyer submits payment |
succeeded | Payment completed | Payment processor confirms the capture |
failed | Payment declined or errored | Payment processor rejects |
expired | Session reached its expiry without completing | Cleanup sweep (see Rules) |
Rules
- A succeeded session cannot become failed, and cannot be charged again: one session pays for one order once. The reverse is allowed.
failedis not terminal; a buyer whose card was declined may pay again on the same session until it expires, and a successful retry converges it tosucceeded. Do not mark an order dead onfailed. Keep listening forcharge.succeeded. - Expired sessions cannot be re-activated; create a new session.
- Session TTL defaults to 30 minutes, but is configurable per session via
expiresIn(from 5 minutes up to 7 days).expiresAtequals the creation time plus that TTL. - A session that passes its
expiresAtwithout completing is moved toexpiredby a periodic cleanup sweep (not at the exact instant the TTL elapses). Only sessions that have not moved money are expired this way.