API Reference
The complete Von Payments Checkout API is documented in OpenAPI 3.1 format.
OpenAPI Spec
openapi.yaml — import into Postman, Insomnia, Redocly, or any OpenAPI tool.
Two front-end integration paths
VORA provides two front-end integration paths. Both vault a vp_pmt_* payment method and settle through Payment Intents — the server-side engine that handles authorization, capture, refunds, and voids. Pick the path that fits your integration:
-
Hosted Checkout (Sessions) —
POST /v1/sessionsreturns a checkout URL. You redirect the buyer to it; Von Payments hosts the card form, handles 3DS, and redirects back to yoursuccessUrl/cancelUrl. Simplest path. -
Embedded Fields — browser SDK (
@vonpay/vora-js) that mounts card / email / address / cardholder elements directly on your page. It renders in two per-session modes, set viaintegrationModeonPOST /v1/sessions(defaultembed):embed— one combined card box renders the whole payment surface; you place onlyemailandsave-for-future-useseparately.elements— you place each element (card, email, address, cardholder, payment-method picker) discretely and style it; the card can be the combined box (number+expiry+cvc together) or the three discretecard-number/card-expiry/card-cvcfields, plus optional standalone Apple Pay / Google Pay buttons. Highest UI control. Where discrete rendering isn't supported, anelementsrequest silently renders asembed. See Render modes: embed vs elements.
Submit behavior (in either mode) depends on how the session is configured: under tokenize-only behavior, submit returns a
vp_pmt_*token your server charges via Payment Intents; under charge-and-save behavior, submit charges and (with a buyer on the session) vaults a reusablevp_pmt_*in one step, while guest sessions charge once and return no token. On charge-and-save you must not also callPOST /v1/payment_intentsfor that session — doing so double-charges. Uses the publishable-key/v1/public/*endpoints from the browser.
Payment Intents (server-side engine) — POST /v1/payment_intents creates an intent your server controls directly for authorization → capture → refund → void. Both front-end paths vault a vp_pmt_* and settle through Payment Intents. Best for delayed-capture flows, fraud-check-before-capture, subscriptions, or direct server-driven transactions where no buyer redirect is needed. See the Payment Intents guide.
See Choose your integration for the side-by-side comparison.
Endpoints Summary
Get session status
GET /v1/sessions/{id} returns the full status of a previously-created session. Requires a secret key (vp_sk_*); publishable keys are rejected with auth_key_type_forbidden. See Session Object for the response shape.
Session statuses
A checkout session has one of five statuses: pending, processing, succeeded, failed, or expired. The normal flow is pending → processing → succeeded (or → failed / → expired); a session may also move directly pending → succeeded/failed/expired. Not all transitions are one-way: a processing session can revert to pending, and a failed session can later converge to succeeded on a successful retry (success takes precedence). Only succeeded and expired are terminal. See Session Object — Status Lifecycle.
Payment intent statuses
Payment intents progress through a discrete lifecycle of six statuses: requires_action → authorized → captured → succeeded (or voided / failed). On the manual-capture path, authorized → captured → succeeded runs as three discrete steps; on the automatic-capture path, an authorized intent can move straight to succeeded when settlement is synchronous. See the Payment Intents guide for the full state machine, transition rules, and error envelope.
Endpoints
This lists the merchant endpoints in the published contract. API error responses carry a docs link that points into this table. Where an endpoint is operator-enabled rather than always available, the row says so — being in the contract is not the same as being switched on for your account.
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /v1/sessions | Publishable or Secret | Create a checkout session (Hosted Checkout) — the only merchant-facing endpoint a publishable key can write through. Eleven /v1/public/* routes require one; the three below are the commonly-used ones, see API Key Types for all of them |
POST | /v1/sessions?dry_run=true | Publishable or Secret | Validate params without creating a session |
GET | /v1/sessions/{id} | Secret | Get session status |
POST | /v1/payment_intents | Secret | Create a payment intent (auth or auth+capture) — see guide |
GET | /v1/payment_intents/{id} | Secret | Retrieve a payment intent's persisted state. This is where you read back the full metadata — the provider truncates long values on the charge itself, but the intent keeps what you sent. Because it comes back verbatim, keep card numbers, bank details and government IDs out of metadata |
POST | /v1/payment_intents/{id}/capture | Secret | Capture an authorized intent (full or partial via amount_to_capture) |
POST | /v1/payment_intents/{id}/void | Secret | Void an authorized intent (before capture) |
GET | /v1/payment_intents/{id}/mirror-order | Secret | Mirrored store-order reference for a discrete-flow intent — see Connected platforms |
POST | /v1/refunds | Secret | Refund a succeeded payment intent (full or partial via amount) |
POST | /v1/tokens | Secret | Mint a vp_pmt_* payment-method token from a provider vault reference (provider_reference) |
POST | /v1/buyers | Secret | Create or update a buyer record (upsert on external_id / email) — see Buyers |
GET | /v1/buyers | Secret | Exact buyer lookup by ?external_id= or ?email= (at least one required; no unfiltered listing) |
GET | /v1/buyers/{id} | Secret | Retrieve a buyer by vp_by_* id |
PATCH | /v1/buyers/{id} | Secret | Sparse buyer profile update (external_id not patchable) |
GET | /v1/capabilities | Publishable or Secret | Per-merchant capability matrix (supported_operations, settlement_currencies, rate_limits) |
GET | /v1/descriptor_config | Secret | Your statement-descriptor configuration — see Statement descriptors |
POST | /v1/mirror/quote | Secret | Price a basket against the connected store before charging — see Connected platforms |
GET | /v1/webhook_subscriptions | Secret | List your webhook subscriptions — see Webhook verification |
POST | /v1/webhook_subscriptions | Secret | Create a webhook subscription |
GET | /v1/webhook_subscriptions/{id} | Secret | Retrieve one subscription |
PATCH | /v1/webhook_subscriptions/{id} | Secret | Update a subscription |
DELETE | /v1/webhook_subscriptions/{id} | Secret | Delete a subscription |
POST | /v1/webhook_subscriptions/{id}/rotate_signing_secret | Secret | Rotate a subscription's whsec_* signing secret — see Webhook secrets |
POST | /v1/webhook_subscriptions/{id}/send_test_event | Secret | Send a test event to a subscription |
GET | /v1/webhook_events/{id} | Secret | Retrieve a single webhook event |
GET | /v1/webhook_listen | Secret | Stream webhook delivery transcripts (Server-Sent Events) — what the CLI's listen command uses. ⚠️ Not generally available — see Streaming endpoints below. Listed because it is in the published contract and the CLI references it |
GET | /v1/wallet-domains | Secret | List your Apple Pay / Google Pay domain verifications — see Apple Pay setup |
POST | /v1/wallet-domains | Secret | Register a wallet domain |
GET | /v1/wallet-domains/{id} | Secret | Retrieve one wallet-domain verification |
DELETE | /v1/wallet-domains/{id} | Secret | Remove a wallet-domain verification (soft delete) |
POST | /v1/wallet-domains/{id}/verify | Secret | Re-run verification for one wallet domain |
GET | /v1/wallet-domains/{id}/file | Secret | Download the Apple Pay domain-verification file to host on your site |
POST | /v1/sdk-telemetry | Secret | Opt-in SDK telemetry. Your code does not call this — the server SDKs post to it for you when telemetry is enabled |
GET | /v1/public/sessions/{id} | Publishable | Browser-safe session lookup — used by Embedded Fields SDK |
POST | /v1/public/binder-load | Publishable | Per-processor element capability map (Embedded Fields) |
POST | /v1/public/tokens | Publishable | Browser-side vp_pmt_* minting (Embedded Fields) |
GET | /v1/public/payment_intents/{id}/events | Signed token | Live payment-status stream for the buyer's browser (Server-Sent Events). ⚠️ Not generally available — see Streaming endpoints below |
GET | /api/health | None | Health check (add ?deep=true for deep variant) |
Endpoints marked "internal" are called by the hosted checkout page, not by merchants. Inbound provider-webhook endpoints exist server-side under /api/webhooks/... for processor-to-platform delivery; they are not part of the merchant API surface and integrators never call them.
Streaming endpoints are not generally available
Two endpoints in the published contract stream events over Server-Sent Events:
| Endpoint | What it would stream |
|---|---|
GET /v1/webhook_listen | Your webhook delivery transcripts — the source for the CLI's listen command |
GET /v1/public/payment_intents/{id}/events | Live payment status to the buyer's browser during checkout |
Neither is switched on. Both answer 503 to every caller, and building against them today will not work. They are documented because they are part of the published contract and the CLI references one of them — not because they are usable.
They fail in two different shapes, which matters if you are writing error handling:
/v1/webhook_listenreturns a normal JSON error envelope withservice_unavailableand aRetry-After: 5header. That header is misleading here — waiting five seconds changes nothing, because this is a configuration state rather than a passing outage./v1/public/payment_intents/{id}/eventsreturns a503with no body at all. Code that assumes every error carries a JSON envelope will throw on the parse rather than read a code.
Turning either on is an infrastructure change on our side, not something you can request per-account, and it needs more than a single switch — there is a second piece of connection configuration that has to be in place as well. There is no date for it. If your integration needs webhook delivery, use webhook subscriptions, which are generally available.
Confirmed against production on 2026-08-09, after that day's release. You can check it yourself without an API key, because the availability check runs before your credentials are read: GET https://checkout.vonpay.com/v1/webhook_listen with no Authorization header returns 503 while it is off, and would return 401 once it is on. A point-in-time reading, not a standing guarantee.
Authentication
Merchant-facing endpoints use Bearer token auth:
Authorization: Bearer vp_sk_live_xxx
Test keys use the vp_sk_test_ prefix. Live keys use vp_sk_live_.
Most endpoints require a secret key (vp_sk_*). The exceptions are session creation (POST /v1/sessions, including its dry_run variant) and GET /v1/capabilities, which accept either key type, and the /v1/public/* endpoints, which require a publishable key (vp_pk_*). So a publishable key can create sessions, read capabilities, and call the public browser endpoints; it is rejected on every secret-only route with auth_key_type_forbidden (HTTP 403).
Request Headers
| Header | Required | Description |
|---|---|---|
Authorization | Yes | Bearer API key. The accepted key type depends on the endpoint — secret (vp_sk_*) for most routes, publishable (vp_pk_*) for the /v1/public/* endpoints, and either type for session creation and GET /v1/capabilities. |
Content-Type | Conditional | application/json on the browser/public POST endpoints (/v1/public/sessions/{id}/confirm, /v1/public/tokens), which reject a missing or non-JSON type with HTTP 415 unsupported_media_type. The authenticated REST POST endpoints (/v1/sessions, /v1/payment_intents, /v1/refunds, /v1/tokens) parse the JSON body without a Content-Type check, but sending application/json is recommended for all POST requests. |
Von-Pay-Version | No | API version date string (e.g. 2026-04-14). If omitted, the request uses the current API version. Pin this header to avoid breaking changes when the API evolves. |
Idempotency-Key | No | Unique key to prevent duplicate operations. If you retry a request with the same key, the original response is returned instead of creating a duplicate. Recommended for all POST requests in production. Max 255 printable-ASCII characters. See Idempotency below for retention. |
Response Headers
Every response includes:
| Header | Description |
|---|---|
X-Request-Id | Unique request ID for debugging |
The three rate-limit headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset are returned on every rate-limited response — both successful responses and 429 responses. Retry-After is returned only on 429 responses. See Rate Limits.
Idempotency
Idempotency-Key is enforced per-resource: a key is unique to (merchant_id, idempotency_key) on the resource it created (session, payment intent, refund). A replayed key with a matching request body returns the original response. A replayed key with a different request body returns HTTP 422 idempotency_replay_incompatible — the SDK surfaces this loudly so a buggy retry that changed amount, cart, or redirect URLs cannot silently inherit a prior resource.
Retention. Idempotency keys are bound to the row they created, not to a separate cache with a fixed TTL. A replayed key returns the same resource for as long as the row is retained on our side — effectively the lifetime of the resource record. Use unique keys per attempt (e.g. order_123_attempt_1, order_123_attempt_2) rather than reusing a key across attempts that could legitimately differ.
Format. Max 255 characters, printable ASCII only (codepoints 0x20–0x7E). Whitespace-only keys are treated as no key.
The server SDKs retry a failed request for you — but not on paymentIntents.create, paymentIntents.capture or refunds.create when the outcome was ambiguous (a 5xx or a timeout) and you sent no key, because a repeat could take the buyer's money twice. Sending a key makes the repeat safe, so the retries stay enabled.
Treat an idempotency key as required on anything that moves money. See Auto-Retry.
Errors
session_already_completed is returned as HTTP 409 Conflict, distinct from session_expired's HTTP 410 Gone. The 409 is not returned by POST /v1/sessions (the create endpoint); it is returned by the public completion and read endpoints — GET /v1/public/sessions/{id}, POST /v1/public/tokens, and POST /v1/public/sessions/{id}/confirm — when a duplicate or late completion lands on a session that has already reached succeeded. Branch on code: never create a new session for a 409 (the buyer already paid — that would re-charge), whereas a 410 means the session TTL elapsed and you may create a fresh one.
Rate Limits
Full bucket list and handling rules: Rate Limits.