Skip to main content

Security

Authentication

API requests use Bearer token authentication:

Key Types

Authorization: Bearer vp_sk_live_xxx
  • Test keys (vp_sk_test_xxx) — test mode, no real money moves
  • Live keys (vp_sk_live_xxx) — live mode; presents your provider's production credentials, real payments
  • Publishable keys (vp_pk_test_xxx / vp_pk_live_xxx) — the browser-side counterpart, safe to ship in a bundle

Mode is decided by the key prefix on every request — one account holds both and flips per request. What a test key actually reaches depends on how your merchant is set up: a sandbox account routes to a Von-owned emulator that contacts no processor at all, while a merchant boarded onto a real processor's sandbox presents that processor's sandbox credentials, selected by mode with no fallback. See Sandbox & Test Mode for the difference — it changes which test cards work.

Keep your secret key secret. If compromised, contact Von Payments to rotate it.

The prefix picks the mode, not the host

_test_ / _live_ selects how the charge is routed, on every request. It does not select which host you call — the same host serves both, and your browser's host comes from apiBaseUrl, which defaults to production and is never inferred from your key. If your browser and server name different hosts, your server calls succeed while every browser call returns 401 auth_invalid_key_publishable. See Which environment am I talking to? and the troubleshooting entry.

Key Rotation

Secret keys can be rotated without downtime. When a key is rotated, the previous key enters a 24-hour grace window during which both keys authenticate. After the grace window closes, the previous key returns 401 with code: auth_key_expired — distinct from auth_invalid_key so SDKs can detect rotation and refresh instead of failing the payment.

  • Plain deactivation (is_active=false with no rotation metadata) returns auth_invalid_key
  • Force-deactivation mid-rotation (flipping is_active=false while grace/expiry metadata is set) returns auth_key_expired — the deactivation is treated as an accelerated rotation, not a plain revocation

Rotate keys via /dashboard/developers/api-keys. The UI shows the new plaintext exactly once at creation — store it immediately.

Confirming the return redirect

When a buyer completes payment they are redirected to your successUrl carrying the session ID:

?session=vp_cs_live_xxx

Treat everything on that URL as untrusted. It arrives through the buyer's browser, so it is visible and modifiable, and reaching the page at all says nothing about whether the payment succeeded — the buyer lands there after the attempt whatever the outcome.

Confirm the payment by looking the session up from your server with your secret key, and take the result from the API response:

const { status } = await client.sessions.get(sessionId);

Then fulfil the order from the charge.succeeded webhook, which reaches you directly rather than through the browser. See Handle the return.

Where signature verification belongs

Signature checking is a real control, but its place is on webhooks — messages Von Payments sends straight to your server, each endpoint carrying its own whsec_* signing secret. See Webhook Signature Verification below.

A signature on a browser redirect can only establish that the message was not altered. It cannot establish that the payment succeeded, because a declined payment is signed exactly as validly as an approved one — so it must never be what gates an order.

PCI Compliance

Von Payments is PCI SAQ-A compliant:

  • Card data is entered in a secure iframe hosted by the payment processor
  • In the live hosted and embedded flows, card numbers, CVVs, and expiry dates never touch Von Payments servers or your servers
  • The checkout page's Content Security Policy prevents any script from reading the payment iframe

You do not need PCI certification to use Von Payments. Both front-end integration paths (Hosted Checkout and Embedded Fields — in either embed or elements mode) using the server-side Payment Intents engine keep real card data inside the iframe boundary; your PCI scope stays at SAQ-A (the simplest level). The only path that would pull you out of SAQ-A is sending raw card data through your own server — and our API does not accept real card data for processing.

Sandbox note: In test mode you can submit a published, synthetic test card number to the sandbox emulator to exercise the flow without real card data. This path is sandbox-only, never reaches a real processor, and does not collect CVV or expiry. Live keys cannot use it.

Data Encryption

DataProtection
Card dataNever stored — entered in processor's iframe
Buyer nameAES-256-GCM encrypted at rest
Buyer emailAES-256-GCM encrypted at rest
API keysSHA-256 hashed in database
Checkout session IDsCryptographically random (vp_cs_<mode>_ + nanoid)

Transport Security

  • Merchant-supplied URLs (such as successUrl and cancelUrl) must use HTTPS; localhost is exempt at the validation layer so you can test locally
  • Live-mode keys reject localhost/loopback redirect URLs — local redirects are permitted only with test-mode keys
  • HSTS header with 2-year max-age
  • TLS 1.2+ only (per the platform's information security policy)

Security Headers

The checkout page serves these headers:

HeaderValue
Strict-Transport-Securitymax-age=63072000; includeSubDomains; preload
X-Frame-OptionsDENY
X-Content-Type-Optionsnosniff
Referrer-Policystrict-origin-when-cross-origin
Content-Security-PolicyRestrictive nonce-based policy allowing only required domains

Rate Limiting

API endpoints are rate-limited. Per-IP is the default axis: when a request carries no credential, the limiter keys on the client IP. For many endpoints the primary axis is instead keyed on the API key, the merchant, or a signed-URL token (with IP used only as a fallback when no credential is present). The session-create endpoint layers a per-API-key axis on top of its per-IP axis. A few internal routes (such as the shallow health check) are unmetered. See Rate Limits for buckets and handling.

API Versioning

The API uses date-based versioning via the Von-Pay-Version header:

Von-Pay-Version: 2026-04-14
  • If the header is omitted (or carries an unrecognized value), the current/latest platform-wide API version is used
  • Pin this header to a specific date to prevent breaking changes when the API evolves
  • New versions are announced in the changelog before becoming the default

Webhook Signature Verification

Webhooks are signed with HMAC-SHA256, keyed with a per-endpoint whsec_* signing secret. The signature ships in the x-vonpay-signature request header with both the timestamp and the HMAC inline:

x-vonpay-signature: t=1714406400,v1=abc123def456...

Verification rules:

  1. Capture the raw request body as bytes before any JSON parsing
  2. Form signed_payload = t + "." + raw_body
  3. Compute v1_expected = lowercase_hex(HMAC_SHA256(key: whsec_secret_as_utf8_bytes, message: signed_payload))
  4. Constant-time compare against each v1= value in the header (the header may carry two v1= entries during a rotation window; accept on any match, reject if there are more than two)
  5. Reject if now - t > 300 (older than 5 min) or t - now > 30 (more than 30 sec in the future)

Reference verifiers in Node, Python, Go, Ruby, and PHP are at Webhook Signature Verification. Use the published verifiers as-is rather than hand-rolling — the canonical implementation handles multi-secret rotation, length-safe constant-time compares, and asymmetric replay-window enforcement.

Important: Each webhook endpoint has its own whsec_* signing secret. It is NOT your API key (vp_sk_*), and it is not the ss_* session secret (which your integration does not use). The webhook signing secret is used only for webhook signatures, and your API key is used only for outbound API auth. The session signing secret backs return URL signatures (and other server-side session integrity checks) — never use it as your webhook or API credential. See Webhook Signing Secrets for the create / rotate / revoke lifecycle.

Reporting Vulnerabilities

If you discover a security vulnerability, contact security@vonpay.com.