Integrate VORA as a Payment Gateway
A one-page reference for platform engineering teams building a Von Payments connector inside their product. Audience: you've already had the partnership conversation (or you're scoping work before one) and you want the API surface mapped against the gateway-adapter shape your platform already uses for other third-party gateways.
What VORA is
VORA exposes three front-end integration paths (Hosted Checkout, Embedded Fields, and Elements) that all settle through Payment Intents, the server-side engine — see Choose your integration for the side-by-side comparison.
For most platform connectors, Payment Intents is the right integration because its discrete auth → capture → void → refund lifecycle maps 1:1 with the gateway-adapter contracts most platforms already have. The rest of this page assumes Payment Intents as your primary integration; the Sessions and Embedded Fields surfaces are covered where they intersect (3DS, webhooks).
Adapter contract mapping
If your platform's adapter contract has discrete auth/capture/void/refund methods, Payment Intents maps directly:
- Auth-only →
POST /v1/payment_intentswithcapture_method: "manual"→ intent reachesauthorized - Capture →
POST /v1/payment_intents/{id}/capture(full or partial viaamount_to_capture) → intent reachessucceeded - Auth + Capture (one-step charge) →
POST /v1/payment_intentswithcapture_method: "automatic"→ intent reachessucceeded - Void →
POST /v1/payment_intents/{id}/void(only before capture) → intent reachesvoided - Refund →
POST /v1/refunds(full or partial viaamount) → refund returns synchronously withstatus: succeeded | pendingplus async webhook confirmation
If you'd rather use Hosted Checkout, the mapping collapses to a single call:
- Auth + Capture → one
POST /v1/sessionscall. Outcome arrives via webhook + signed redirect. - Void → not applicable. Sessions that don't complete transition to
expiredorfailed. - Refund → initiated from the merchant's dashboard (or upstream); delivered to your webhook as a
charge.refundedevent.
API surface — what your connector calls
Base URL
- Production:
https://checkout.vonpay.com - Sandbox: same base URL; key prefix (
vp_sk_test_*vsvp_sk_live_*) selects the environment. Test keys cannot accidentally hit live data and vice versa.
Headers
Required:
Authorization: Bearer <vp_sk_*>— the merchant's secret API keyContent-Type: application/json
Strongly recommended (server accepts requests without them, but you should always send):
Von-Pay-Version: 2026-04-14— pin the API version. Omitting this header tracks the latest stable, which can change behind you.Idempotency-Key: <unique-string>— see Idempotency below. Not server-enforced today, but every connector should send one to make retries safe.
Endpoints
| Verb | Path | Purpose |
|---|---|---|
| Sessions (Hosted Checkout) | ||
POST | /v1/sessions | Create a checkout session. Returns id, checkoutUrl, expiresAt (30-min TTL) |
GET | /v1/sessions/{id} | Retrieve session status — pending / processing / succeeded / failed / expired |
| Payment Intents (discrete lifecycle) | ||
POST | /v1/payment_intents | Create an intent with capture_method: "automatic" (auth+capture) or "manual" (auth-only) |
POST | /v1/payment_intents/{id}/capture | Capture an authorized intent (full or partial via amount_to_capture) |
POST | /v1/payment_intents/{id}/void | Void an authorized intent (before capture) |
POST | /v1/refunds | Create a refund (full or partial via amount); references the intent via payment_intent |
POST | /v1/tokens | Mint a vp_pmt_* payment-method token from an iframe-vault handle |
| Capability discovery | ||
GET | /v1/capabilities | Per-merchant capability matrix — supported_operations, settlement_currencies, rate_limits (snake_case). Read once at startup; gate optional operations on this. |
| Public (publishable-key) endpoints — used by Embedded Fields browser SDK | ||
GET | /v1/public/sessions/{id} | Session lookup with a publishable key (browser-safe) |
POST | /v1/public/binder-load | Per-processor element capability map |
POST | /v1/public/tokens | Browser-side vp_pmt_* minting |
| Operational | ||
GET | /api/health | Liveness probe. No auth required. |
GET | /.well-known/vonpay.json | Discovery metadata. No auth required. |
The full request/response shapes are in the REST API reference (downloadable OpenAPI spec). For a typed SDK surface, see the Node and Python SDKs — your adapter doesn't need to use them, but they're a working reference for request shapes.
Webhook events your endpoint receives
Configured by the merchant in their dashboard — they register one or more endpoint URLs at /dashboard/developers/webhooks and subscribe each to the event types your platform needs. Your platform's webhook URL receives:
| Event | When fired | data payload |
|---|---|---|
| Charges | ||
charge.succeeded | Buyer completed a hosted-checkout payment OR a direct /v1/payment_intents charge succeeded | session_id, transaction_id, amount, currency |
charge.failed | Charge attempt failed | session_id, transaction_id, amount, currency, failure_reason |
charge.refunded | A refund settled against a prior charge (full or partial; fires once per refund) | session_id, transaction_id, amount, currency |
| Payment Intents | ||
payment_intent.succeeded | Intent reached succeeded (auto-capture, manual capture, or post-3DS settle) | session_id, transaction_id, amount, currency |
payment_intent.failed | Intent reached failed | session_id, transaction_id, amount, currency, failure_reason |
payment_intent.cancelled | Intent was voided before capture (the object's status field reports voided; the event name is cancelled — same terminal state) | session_id, transaction_id, amount, currency |
Every event is wrapped in the envelope {id, type, created, livemode, merchant_id, data} — full per-event shapes at Webhook Events. New event families (dispute.*, application.*, payout.*) are in the subscription catalog but not in the public platform-integrator surface today.
There is no session.expired / "buyer abandoned" event today — closing this gap is on the roadmap. For abandoned-cart detection in the meantime, poll GET /v1/sessions/{id} after the 30-minute TTL.
Webhook signature format
HMAC-SHA256 over t.<raw-body>, keyed with the per-endpoint whsec_* signing secret minted at endpoint registration.
x-vonpay-signature: t=<unix_seconds>,v1=<lowercase_hex_hmac_sha256>
- Algorithm: HMAC-SHA256
- Key: the endpoint's
whsec_*secret, raw UTF-8 bytes (do not base64-decode, do not strip the prefix) - Message:
t + "." + raw_body— thetvalue from the header concatenated with the raw HTTP request body before any parsing - Replay window: asymmetric — reject if
now - t > 300(past) ort - now > 30(future) - Multi-secret: during a rotation window the header carries two
v1=entries; accept on any match. Reject headers with more than twov1=entries
Reference verifier code in five languages (Node, Python, Go, Ruby, PHP) is on the Webhook Verification page. Always use a constant-time comparison helper (hmac.compare_digest, crypto.timingSafeEqual, subtle.ConstantTimeCompare, etc.) — variable-time == leaks the secret a byte at a time under repeated-request timing attacks.
3DS handoff
How 3DS surfaces depends on which integration path the merchant uses.
Hosted Checkout (Sessions)
3DS is transparent. The hosted page renders the issuer's challenge inline:
- On success, the session continues to
succeededand your webhook fires normally. - On failure, the session moves to
failedwithfailureCodeindicating 3DS rejection.
Your adapter's "3DS challenge" code path typically has nothing to do — the encapsulated flow swallows it. If your adapter contract requires a separate "3DS in progress" state, poll GET /v1/sessions/{id} while the session is pending.
Payment Intents
The connector handles the challenge. When the issuer requires 3DS, the intent returns status: "requires_action" with a next_action block:
{
"type": "redirect_to_url",
"redirect_to_url": { "url": "https://challenge.example/3ds/abc123" }
}
Redirect the buyer to next_action.redirect_to_url.url as a top-level navigation (not inside an iframe — banks block this). After the challenge, learn the outcome via the payment_intent.succeeded / payment_intent.failed webhook (there is no GET /v1/payment_intents/{id} retrieve endpoint). Don't trust the browser's return signal as authoritative.
Embedded Fields + Payment Intents
If the merchant pairs Embedded Fields with Payment Intents, the browser SDK can drive the 3DS modal inline using a client_confirm: { binder, client_secret } block that POST /v1/payment_intents returns on status === "requires_action". The connector forwards client_confirm to the browser; the SDK does the rest. See Embedded Fields — 3D Secure.
Idempotency
Every POST request (/v1/sessions, /v1/payment_intents, /v1/payment_intents/{id}/capture, /v1/payment_intents/{id}/void, /v1/refunds, /v1/tokens) should carry an Idempotency-Key header with a unique-per-operation value (typically your platform's internal order ID + a stable per-attempt suffix). The header is not server-enforced on every endpoint today — requests without it succeed — but a connector that omits it cannot make POST operations safely retryable, which matters under transient network failures.
- Replaying the same
Idempotency-Keyreturns the original response — including the originalcheckoutUrl— for as long as the session record is retained (today, effectively the lifetime of the row), not a fixed 24-hour cache. No duplicate session is created. - Reusing the same
Idempotency-Keywith a different request body returns422withcode=idempotency_replay_incompatible.
Recommended pattern for adapters:
Idempotency-Key: {platform_short_name}_{merchant_internal_order_id}_{attempt_count}
Example: sticky_order-789012_attempt-1. On retry after a transient failure, increment the suffix to get a fresh session; on retry of the same logical operation, keep the suffix to dedupe.
Error code catalog
All errors return JSON with error, code, fix, and docs fields, plus an X-Request-Id header for correlation:
{
"error": "API key is malformed or does not exist",
"code": "auth_invalid_key",
"fix": "Check that your API key is correctly formatted and active",
"docs": "https://docs.vonpay.com/reference/error-codes#auth_invalid_key"
}
The full ErrorCode catalog is at Error Codes. The codes most relevant to a connector:
| HTTP | Code | Common adapter handling |
|---|---|---|
| 401 | auth_invalid_key | Surface to the merchant — their pasted key is wrong or rotated past grace |
| 401 | auth_key_expired | Surface to the merchant — they need to update the configured key |
| 401 | auth_merchant_inactive | Surface to the merchant — Von Payments has disabled the account |
| 403 | merchant_not_onboarded | The merchant hasn't completed KYC — surface a "complete onboarding" link |
| 422 | merchant_not_configured | Surface to the merchant — payment routing isn't fully set up on their side |
| 400 | validation_invalid_amount | Adapter bug — fix the amount mapping (must be positive integer minor units) |
| 400 | validation_missing_field | Adapter bug — required field missing in the request body |
| 409 | session_wrong_state | Idempotency — the session is in a state that disallows this operation |
| 410 | session_expired | Session's 30-min TTL elapsed; create a new one |
| 429 | rate_limit_exceeded / rate_limit_exceeded_per_key | Back off per the Retry-After header. Per-IP bucket is 10 req / 60 s; per-API-key is 30 req / 60 s on session-creates. |
| 502 | provider_unavailable | Upstream payment provider is unreachable. Retry with exponential backoff. |
| 402 | provider_charge_failed | Card declined — surface the decline to the merchant's UI |
A connector that handles auth_*, merchant_*, validation_*, and provider_* error families idiomatically is structurally complete. The remaining codes are operational (rate limits, idempotency conflicts, internal errors) and should be retried per standard exponential-backoff practice.
Sandbox
Every Von Payments merchant has a sandbox with deterministic mock outcomes. See Sandbox & Test Mode for the outcome matrix, and the Platform Integrator Sandbox guide for the provisioning walkthrough — keys in under a minute, no KYC.
Reference adapter implementations
A multi-tenant Node.js reference adapter is published at github.com/Von-Payments/vonpay-samples/tree/main/platform-integrator-nextjs (MIT-licensed). It demonstrates the full session lifecycle for a platform serving many merchants:
- Per-tenant credential lookup (each onboarded merchant has its own
vp_sk_*+ss_*stored on the platform side;lib/tenants.tsis shaped like a real DB query) - Tenant-scoped session creation with
Idempotency-Keyand pinnedVon-Pay-Version - Tenant-scoped return-URL signature verification (uses the right tenant's
ss_*) - Multi-tenant webhook routing — single
/api/webhooksendpoint receives events for all tenants, peeks atmerchantIdto route, then verifies HMAC with the tenant'svp_sk_* - Composite-key event idempotency dedup (Webhooks v1 doesn't emit a top-level event id; the sample composes
sessionId:event:timestampand stores in-memory — swap for Redis in production) - A small CRM-style UI with 3 simulated tenants → customers → "Charge $X" flow
The general-purpose Node.js single-merchant samples (checkout-nextjs, checkout-express, checkout-paybylink-nextjs) cover the SDK surface against a single merchant if your platform ships its own multi-tenancy layer.
The Node adapter at platform-integrator-nextjs documents the patterns to port into a PHP gateway-adapter contract. Use the REST API reference directly for raw HTTP, or use vonpay-checkout (Python SDK) if your platform is Python-based.
Account model
Each merchant of yours is a top-level Von Payments merchant with their own keys, dashboard, and webhook routing — same shape as every gateway integration your platform already supports. The merchant brings their per-merchant credentials; your platform stores them per-merchant; your adapter calls VORA server-to-server with the right merchant's key for each transaction.
Partnership
The technical spec on this page is what your eng team needs to build the connector. Getting it listed in your platform's gateway dropdown is a separate partnership conversation — see Going from sandbox to a partnership.
Related
- Platform Integrator Sandbox — get keys, no KYC, in under a minute
- Quickstart — the 5-minute walkthrough; the
vp_sk_test_*you create there is the same one your connector will exercise - Webhook Verification — full HMAC verification scheme + reference code in 5 languages
- Error Codes — full ErrorCode catalog
- API Keys — key types, rotation, revocation
- Sandbox & Test Mode — sandbox behavior contract + outcome matrix
- REST API — full request/response shapes + OpenAPI spec