Skip to main content

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.

Everything on this page is what's live today. Sections that depend on near-term checkout work are flagged explicitly — don't build against them yet.

What Vora is, in one paragraph

Vora is a hosted-checkout payment product. Your merchant calls POST /v1/sessions server-side with an amount and a successUrl. We return a checkoutUrl. The buyer is redirected there, completes payment on a Von-hosted page (cards, Apple Pay, Google Pay, Klarna, etc.), and is redirected back to the merchant's successUrl with a signed query string. A signed webhook (session.succeeded, session.failed, session.expired, refund.created) confirms the outcome server-to-server.

There is no separate auth → capture → void → refund lifecycle on the API surface. The hosted-checkout session encapsulates auth-and-capture in a single state machine: pending → succeeded | failed | expired. If your platform's adapter interface expects discrete auth/capture/void operations, your connector maps:

  • Auth + Capture → one POST /v1/sessions call. Outcome is reported via webhook + signed redirect.
  • Void → not applicable. Sessions that don't complete simply transition to expired or failed.
  • Refund → today, refunds are initiated upstream (via your merchant's Von Payments dashboard or via an upstream-processor refund) and delivered to your platform's webhook endpoint as a refund.created event. A merchant-initiated refund API is on the roadmap; see What's not in this spec yet below.

If your platform's adapter contract requires you to return a void/refund result synchronously, this is the fundamental shape mismatch you'll need to handle in your adapter layer (typically: store an idempotency-key for refund intents, surface pending until the webhook lands).

API surface — what your connector calls

Base URL

  • Production: https://checkout.vonpay.com
  • Sandbox: same base URL; key prefix (vp_sk_test_* vs vp_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 key
  • Content-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 today

VerbPathPurpose
POST/v1/sessionsCreate a checkout session. Returns id, checkoutUrl, expiresAt (30-min TTL)
GET/v1/sessions/{sessionId}Retrieve session status — pending / succeeded / failed / expired / refunded
GET/v1/healthLiveness probe. No auth required.
GET/v1/discoveryDiscovery metadata (capabilities, supported currencies). No auth required.

The full request/response shapes are in the REST API reference and the OpenAPI spec (downloadable). 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. Your platform's webhook URL receives:

EventWhen firedPayload includes
session.succeededBuyer completed payment, funds capturedsessionId, transactionId, amount, currency, merchantId, timestamp
session.failedBuyer attempted payment and failedsessionId, failureCode, error, amount, currency, merchantId, timestamp
session.expiredSession passed its 30-min TTL with no completionsessionId, amount, currency, merchantId, timestamp
refund.createdA refund was issued against a previously-succeeded sessionsessionId, refundId, transactionId, amount, currency, merchantId, timestamp

Full event shapes: Webhook Events. Verification scheme: Webhooks.

Webhook signature format

Today: HMAC-SHA256 over the raw request body, with the merchant's API key as the secret. Header is a single hex string.

X-VonPay-Signature: <lowercase-hex-hmac-sha256>
X-VonPay-Timestamp: <ISO-8601-UTC>
  • Algorithm: HMAC-SHA256
  • Key: the merchant's vp_sk_* API key, raw UTF-8 bytes (do not base64-decode, do not strip the prefix)
  • Message: the raw HTTP request body, byte-for-byte before any parsing
  • Replay window: ±5 minutes against X-VonPay-Timestamp

A Webhooks v2 format (x-vonpay-signature: t=<unix-ts>,v1=<hex> over t.body with per-subscription whsec_* secrets) is queued for an upcoming release. It is not active today. When v2 ships, your connector will be able to opt into per-subscription secrets — useful for rotating without touching the merchant's API key. See Webhook Verification for the full v1 + v2 walkthrough; today, implement v1 and ignore Section 2 of that page.

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

3DS is handled inside the hosted-checkout page; your connector does not need to deal with it directly. When the buyer's card requires 3DS:

  • The hosted page renders the issuer's challenge inline.
  • On success, the session continues to succeeded and your webhook fires normally.
  • On failure, the session moves to failed with failureCode indicating 3DS rejection.

Your adapter's "3DS challenge" and "soft-decline retry" code paths typically don't have anything to do — the encapsulated flow swallows them. The exception: if your adapter contract requires you to report a 3DS-required state separately, you can detect it by polling GET /v1/sessions/{id} while the session is still pending and surfacing a "3DS in progress" state to your platform's UI. Most adapters will simply wait for the terminal webhook.

Idempotency

Every POST /v1/sessions request should carry an Idempotency-Key header with a unique-per-session value (typically your platform's internal order ID + a stable per-attempt suffix). The header is not server-enforced today — requests without it succeed — but a connector that omits it cannot make session creation safely retryable, which matters under transient network failures.

  • Replays of the same Idempotency-Key within 24 hours return the original response — including the original checkoutUrl. No duplicate session is created.
  • After 24 hours, the key may be reused; a new session is created.
  • Different request bodies with the same Idempotency-Key within 24 hours return 409 Conflict with code=idempotency_key_collision.

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 27-code catalog is at Error Codes. The codes most relevant to a connector:

HTTPCodeCommon adapter handling
401auth_invalid_keySurface to the merchant — their pasted key is wrong or rotated past grace
401auth_key_expiredSurface to the merchant — they need to update the configured key
401auth_merchant_inactiveSurface to the merchant — Von Payments has disabled the account
403merchant_not_onboardedThe merchant hasn't completed KYC — surface a "complete onboarding" link
422merchant_not_configuredSurface to the merchant — payment routing isn't fully set up on their side
400validation_invalid_amountAdapter bug — fix the amount mapping (must be positive integer minor units)
400validation_missing_fieldAdapter bug — required field missing in the request body
409session_wrong_stateIdempotency — the session is in a state that disallows this operation
410session_expiredSession's 30-min TTL elapsed; create a new one
429rate_limit_exceeded / rate_limit_exceeded_per_keyBack 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.
502provider_unavailableUpstream payment provider is unreachable. Retry with exponential backoff.
402provider_charge_failedCard 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 outcome matrix

For dev-loop testing your connector, every Von Payments merchant has a sandbox environment with deterministic mock outcomes. The session amount field selects the outcome:

Amount (minor units)OutcomeWebhook fired
200Card declined (card_declined)session.failed with failureCode: card_declined
Any otherPayment succeedssession.succeeded with transactionId

The mock gateway is intentionally narrow — one decline trigger, otherwise approve. For richer card-acceptance testing (3DS challenges, issuer-specific declines, timeouts), board a real third-party gateway test-mode account onto your sandbox merchant — see your account manager for the supported providers and the test-card catalog each one ships with.

For getting set up with a sandbox in the first place — start at vonpay.com/developers, click Activate Vora Sandbox — see the Platform Integrator Sandbox guide.

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.ts is shaped like a real DB query)
  • Tenant-scoped session creation with Idempotency-Key and pinned Von-Pay-Version
  • Tenant-scoped return-URL signature verification (uses the right tenant's ss_*)
  • Multi-tenant webhook routing — single /api/webhooks endpoint receives events for all tenants, peeks at merchantId to route, then verifies HMAC with the tenant's vp_sk_*
  • Composite-key event idempotency dedup (Webhooks v1 doesn't emit a top-level event id; the sample composes sessionId:event:timestamp and 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.

PHP adapter

A PHP reference adapter targeting the gateway-interface shape used by subscription-billing CRMs is on the roadmap. Until it ships, the Node adapter at platform-integrator-nextjs documents the same patterns; port them into your PHP gateway-adapter contract using the REST API reference directly, or use vonpay-checkout (Python SDK) if your platform is Python-based.

What's not in this spec yet

These are real, but the public surface for them is in flight. Don't build against them today — your connector will need to revise once the surface lands.

  • Merchant-initiated refund API. Today refunds flow webhook → your platform. A POST /v1/sessions/{id}/refund (or equivalent) for merchant-initiated partial/full refunds is on the roadmap. When it ships, the refund.created webhook shape will not change — your connector continues to consume that — but you'll have an outbound API surface for refund initiation as well.
  • Webhooks v2 (whsec_* per-subscription signing secrets, t=<ts>,v1=<hmac> header). Walkthrough is documented at Webhook Verification → Section 2; the delivery engine is queued.
  • Per-platform parent account with rolled-up reporting across the platform's customer merchants. Not on the immediate roadmap; today each merchant of yours is a top-level Von Payments merchant with its own keys, dashboard, and webhook routing.
  • Connector marketplace / app store / developer portal review. Not happening near-term. Your platform's connector lives in your codebase; we list partnerships once they sign.

Partnership process

The work above is what your platform's eng team builds. The work that gets your connector listed in your platform's gateway dropdown is a separate biz-dev partnership conversation:

  • We list Von Payments in your platform's gateway-config UI alongside the other gateways you support.
  • Our sales team routes deal flow to merchants who use your platform.
  • Rev-share terms and a support channel are agreed in writing.

Reach out via your existing Von Payments contact, or through the Quickstart → "I'm a developer evaluating Vora" path. The technical spec on this page is what your eng team needs; the partnership conversation is what gets the connector live.