Security
Authentication
API requests use Bearer token authentication:
Key Types
Authorization: Bearer vp_sk_live_xxx
- Test keys (
vp_sk_test_xxx) — sandbox only, no real charges - Live keys (
vp_sk_live_xxx) — production, real payments
Keep your API key secret. If compromised, contact Von Payments to rotate it.
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=falsewith no rotation metadata) returnsauth_invalid_key - Force-deactivation mid-rotation (flipping
is_active=falsewhile grace/expiry metadata is set) returnsauth_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.
HMAC Return URL Signatures
When a buyer completes payment and is redirected to your successUrl, the URL includes a signature:
?session=vp_cs_live_xxx&status=succeeded&amount=1499¤cy=USD&transaction_id=txn_abc&sig=a1b2c3...
Algorithm
sig = HMAC-SHA256(
key: VON_PAY_SESSION_SECRET,
data: "{session}.{status}.{amount}.{currency}.{transaction_id}"
)
The signature is the lowercase-hex digest. When transaction_id is absent, an empty string is substituted in its place (the data string uses nullish coalescing — only null/undefined become "").
Verification
Always verify server-side. The return URL is visible to the buyer and can be modified.
Use crypto.timingSafeEqual (or equivalent) to prevent timing attacks:
import crypto from "crypto";
function verify(session, status, amount, currency, transactionId, sig, secret) {
const data = `${session}.${status}.${amount}.${currency}.${transactionId ?? ""}`;
const expected = crypto.createHmac("sha256", secret).update(data).digest("hex");
return crypto.timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"));
}
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. All three front-end integration paths (Hosted Checkout, Embedded Fields, and Elements) 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
| 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) |
| Return URL signatures | Deterministic HMAC-SHA256, keyed by the session signing secret |
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
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:
- Capture the raw request body as bytes before any JSON parsing
- Form
signed_payload = t + "." + raw_body - Compute
v1_expected = lowercase_hex(HMAC_SHA256(key: whsec_secret_as_utf8_bytes, message: signed_payload)) - Constant-time compare against each
v1=value in the header (the header may carry twov1=entries during a rotation window; accept on any match, reject if there are more than two) - Reject if
now - t > 300(older than 5 min) ort - 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 NOT the session signing secret (ss_*). 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.