Security
Authentication
API requests use Bearer token authentication:
Key Types
Authorization: Bearer vp_sk_live_xxx
Von Payments issues two kinds of API key you authenticate with and two kinds of signing secret you never send in a request. Older accounts may also hold a fifth, legacy key — see the note below the list:
- Secret key (
vp_sk_test_…/vp_sk_live_…) — server-side only, authenticates every API call. A live key presents your provider's production credentials and moves real money. - Publishable key (
vp_pk_test_…/vp_pk_live_…) — the browser-side counterpart, safe to ship in a bundle. - Session signing secret (
ss_…) — older accounts only; it is no longer issued, and nothing in the integration asks you for it. In particular it does not confirm a return redirect — those signatures are produced with a single platform-wide secret, so anss_*passed to a return verifier fails every time. - Webhook signing secret (
whsec_…) — verifies the webhooks we send you. Per endpoint, and not your API key. See Webhook signing secrets.
⚠ Older accounts may also hold a vp_key_… key. It predates the current
prefixes and is still accepted — as a full secret key. If you find one in a
bundle or a config, treat it as live credentials and rotate it to a vp_sk_ key;
do not assume it is inert because it is not in the list above.
Prefixes, which key each route accepts, rotation and expiry: API keys. Mode is decided by the key prefix on every request — one account holds both and flips per request; a test key never moves real money (Test mode). The prefix picks the mode, not the host: the same host serves both, and a browser whose apiBaseUrl names a different host than your server gets 401 auth_invalid_key_publishable on every call (troubleshooting). A compromised secret key is revoked, with no grace window, from the dashboard: Compromise — skip the grace.
Key Rotation
Secret keys can be rotated without downtime. When a key is rotated, the previous key enters a grace window that is caller-configurable — 1h, 24h or 7d, defaulting to 24h — 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.
- A revoked key (no rotation metadata) returns
auth_invalid_key; a key deactivated mid-rotation returnsauth_key_expired
Rotate keys via /dashboard/developers/api-keys. The UI shows the new plaintext exactly once — store it immediately.
Confirming the return redirect
The buyer returns to your successUrl with ?session=vp_cs_… whatever the outcome, through their own browser — treat everything on that URL as untrusted, and reaching the page proves nothing. Read the outcome from sessions.get with your secret key and fulfil on the charge.succeeded webhook: Handle the return. Signature checking belongs on webhooks, not on the redirect — a declined payment is signed exactly as validly as an approved one.
PCI Compliance
Card number, expiry and CVC are entered in a secure iframe hosted by the payment processor and never touch Von Payments' servers or yours, on Hosted Checkout and on Embedded Fields in either mode — your PCI scope stays at SAQ-A.
elements mode puts billing-details fields in YOUR pageThe card fields stay in the iframe in both modes, but in elements mode the SDK renders cardholder name, email, the save-card checkbox and (by default) the billing address as ordinary inputs in your own DOM — the cardholder-name input even carries autocomplete="cc-name", so the browser may autofill it from the buyer's saved card. A connection whose capability map declares the address native gets the processor's own address element instead, rendered in its isolated iframe; the capability matrix says which yours declares.
Any other script on that page can read those values. Your SAQ-A eligibility is unchanged, but your page-script hygiene (a tag manager, an analytics vendor, a compromised dependency) is now in scope for buyer identity data. If you cannot vouch for every script on your checkout page, prefer embed mode.
Data Encryption
| Data | Protection |
|---|---|
| Card data | Never stored — entered in processor's iframe |
| Buyer name | AES-256-GCM encrypted at rest |
| Buyer email | AES-256-GCM encrypted at rest |
| API keys | SHA-256 hashed in database |
| Checkout session IDs | Cryptographically random (vp_cs_<mode>_ + nanoid) |
Transport Security
- Merchant-supplied URLs (such as
successUrlandcancelUrl) must use HTTPS;localhostis 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:
| Header | Value |
|---|---|
Strict-Transport-Security | max-age=63072000; includeSubDomains; preload |
X-Frame-Options | DENY |
X-Content-Type-Options | nosniff |
Referrer-Policy | strict-origin-when-cross-origin |
Content-Security-Policy | Restrictive nonce-based policy allowing only required domains |
Rate Limiting
Every endpoint is rate-limited per IP, per API key, or both — buckets, limits and the 429 headers are on Rate Limits.
API Versioning
The API is versioned by date through the Von-Pay-Version request header — API Versioning.
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...
The exact bytes to sign, the replay window and working verifiers in Node, Python, Go, Ruby and PHP are on Webhook Signature Verification. The platform signs with exactly one secret and sends one v1= entry, so there is no rotation grace window on our side — Rotating a signing secret. Each endpoint's whsec_* is its own secret: not your API key, and not the ss_* session secret.
Reporting Vulnerabilities
If you discover a security vulnerability, contact security@vonpay.com.