API Key Types
Self-service vs. gated issuance
Key issuance depends on mode:
- Test keys (
vp_sk_test_*,vp_pk_test_*,ss_test_*) — fully self-service. Start at vonpay.com/developers (or deep-link straight to app.vonpay.com/dashboard/developers if signed in), click Activate VORA Sandbox, and your test keys are issued in seconds — no ops-side approval queue. A sandbox merchant record is seeded automatically and pre-wired for sandbox transactions so you can create and route test sessions immediately. - Live keys (
vp_sk_live_*,vp_pk_live_*,ss_live_*) — gated behind merchant application approval. You must complete onboarding and have your merchant application approved (KYC + contract) before live-mode keys can be generated. Contact Von Payments to start the merchant onboarding process. Until your merchant account is approved for live payments, requesting live keys returns403 merchant_not_onboardedwith afixstring pointing back to the onboarding flow ("Complete the merchant application and wait for ops approval before creating live keys. Use test keys (mode: 'test') during integration."). A denied account gets a distinctfixmessage asking you to contactsupport@vonpay.com. See Error Codes →merchant_not_onboarded.
Key types at a glance
| Key | Prefix | Where to use it | Rotation |
|---|---|---|---|
| Test secret key | vp_sk_test_ | Server-side code, test mode only | Grace window (default 24h) |
| Live secret key | vp_sk_live_ | Server-side code, production only | Grace window (default 24h) |
| Test publishable key | vp_pk_test_ | Browser-exposed client code, test mode only | Grace window (default 24h) |
| Live publishable key | vp_pk_live_ | Browser-exposed client code, production only | Grace window (default 24h) |
| Session secret | ss_test_, ss_live_ | Issued with your keys; not used by your integration | Rotated independently |
It is provisioned alongside your API keys and appears in the dashboard, but nothing in the integration asks you for it — in particular it is not how you confirm a payment when the buyer is redirected back. Read the outcome from sessions.get and fulfil on the session.succeeded webhook instead; see Handle the return.
If you have it in a secret manager there is nothing to change today. Do not build new integration on it.
Webhook signing secrets (whsec_*) are separate — see Webhook Signing Secrets.
Secret vs publishable — when to use which
-
Secret keys (
vp_sk_*) can create sessions, retrieve session status, and do everything through the API. Never expose them to a browser or mobile app. -
Publishable keys (
vp_pk_*) are safe to embed in client code. They can initialize the drop-invora-hosted.jswidget and create sessions —POST /v1/sessionsis the only merchant-facing endpoint a publishable key can write through. One read-only route also accepts either key type:GET /v1/capabilities. Every other/v1/merchant route rejects a publishable key withauth_key_type_forbidden(HTTP 403), including retrieving the full session details a secret key sees.Session creation is not the only thing a publishable key does. Eleven browser-facing
/v1/public/*handlers require a publishable key and reject a secret one — so the reach of a publishable key is wider than the merchant surface suggests:Route Method What it does /v1/public/sessions/{id}GETRedacted public session view (id, expiry, amount, currency, routing — no PII, no merchant-internal metadata, no provider session id) /v1/public/sessions/{id}/embed-tokenGETShort-lived token for the embedded-fields frame /v1/public/sessions/{id}/mirror-orderGETMirrored store-order status for this session /v1/public/sessions/{id}/chargePOSTCharges the buyer — Embedded Fields charge-at-submit. Availability has three separate gates, and they do not share a status code: the feature is off platform-wide ( 501), your gateway does not support it (422 merchant_not_configured), or your gateway is not yet live-enabled (501)/v1/public/sessions/{id}/completePOSTCompletes the session after payment. Gated by a platform feature flag that is checked before authentication, so while it is off every caller gets 503 feature_unavailableregardless of key type/v1/public/sessions/{id}/confirmPOSTConfirms a sandbox test-mode session. A live-mode call is refused with 422 merchant_not_configured— it can never charge a real gateway/v1/public/binder-loadPOSTPer-processor element capability map /v1/public/tokensPOSTMints a vp_pmt_*payment-method token from the browser — and on a wallet request (instrument: "wallet") charges the buyer directly/v1/public/telemetry/browserPOSTBrowser telemetry ingest /v1/public/wallets/apple-sessionPOSTApple Pay merchant validation for standalone wallets, enabled per account /v1/public/wallets/google-sessionPOSTGoogle Pay session for standalone wallets, enabled per account The session routes are session-bound — each is scoped to one checkout session, and a cross-merchant, mode-mismatched, or unknown session collapses to an opaque
404. That scoping, not the key type, is what limits them. (/v1/public/telemetry/browseris the exception: it takes no session and is rate-limited per IP instead.)Because your publishable key is printed into every public checkout page by design, treat everything in this table as reachable by anyone who loads that page. A publishable key identifies your account; it is not an access control.
Key type is derived authoritatively from the prefix: vp_pk_ ⇒ publishable, vp_sk_ ⇒ secret. The checkout API enforces key-type restrictions server-side. For example, calling sessions.get() (GET /v1/sessions/:id, the full secret-key view) with a publishable key returns auth_key_type_forbidden (HTTP 403). The SDK surfaces that 403; it does not reject the key at construction time — constructing a client with a publishable key is allowed.
Rotation grace
When you rotate a secret or publishable key, the old key stays valid for a grace window so you can deploy the new key without downtime. The grace period is caller-configurable — 1h, 24h, or 7d — and defaults to 24h when you don't specify one. The old key keeps working until grace_ends_at (which is set to NOW() plus the requested interval), after which it rejects with auth_key_expired.
Rotation timeline (default 24h grace)
| t = | State |
|---|---|
| t0 | Click Rotate in /dashboard/developers/api-keys. New key is created; old key enters grace. |
| t0 | UI shows the raw value of the new key once. Copy it to your secret manager immediately. |
| t0 → t0+24h | Both keys accepted. Deploy the new key across all your services during this window. |
| t0+24h | Old key rejects with auth_key_expired (HTTP 401). Grace ends. |
If you rotate with a 1h or 7d grace, the t0+24h row shifts to t0+1h or t0+7d accordingly.
You cannot re-rotate a key that is already in its grace window — that returns 404 ("not eligible for rotation"); rotate the current primary key instead. A predecessor already in grace stays active until its own grace_ends_at passes; rotating again does not deactivate it early. There is no per-window rotation rate limit (24h is the default grace period, not a cooldown), though each mode has a cap on the number of simultaneously-active keys (fresh + in-grace) — exceeding it returns 409.
Compromise — skip the grace
If a key is exposed (leaked to a public repo, screenshot, shoulder-surf, etc.), do not initiate a normal rotation. Grace would keep the compromised key working for the duration of the grace window.
Instead, from /dashboard/developers/api-keys:
- Click Revoke on the compromised key (not Rotate). This soft-deletes the key (
is_active = false) so it rejects on the very next request — no grace. - Create a fresh key.
- Rotate deployed services to the fresh key.
- Von Payments also flags the revoked key internally so downstream audit logs record the revocation.
A revoked key (one with no rotation metadata) rejects with auth_invalid_key. A key that simply ran out its rotation grace rejects with auth_key_expired.
Rotation-badge states (dashboard)
The /dashboard/developers/api-keys UI shows a badge on each key reflecting its rotation state. Useful when you're debugging why a deploy is still getting authentication errors somewhere:
- Active — the primary key, created or rotated-into most recently.
- Grace: ends in <N>h — previous primary, still accepted until
grace_ends_at. - Revoked — manually revoked via Revoke. Will never accept again (
auth_invalid_key). - Expired — grace window passed naturally (
auth_key_expired).
If a live-mode service suddenly starts failing authentication after a rotation, check the badge on the key that service is configured with. "Expired" means you missed the grace window for at least one deploy.
Expiry behavior
API keys do not have a baked-in TTL — they stay Active until rotated or revoked. The only paths to expiry are:
- Normal rotation → previous key enters its grace window → rejects with
auth_key_expiredoncegrace_ends_atpasses. - Revoke →
is_active = false, immediate rejection withauth_invalid_key. - Merchant account suspension → all keys for that merchant immediately reject with
auth_merchant_inactive(HTTP 401). This is an account-level state (the merchant account is suspended or no longer approved for the mode), separate from per-key rotation/revocation. - Manual rotation forced by ops — e.g. response to a breach report. Shows the same Revoked badge.
Key verification reads straight from the database on every request — there is no separate replication cache to wait on. A freshly-rotated key is live as soon as the rotation write commits.
Related
- Webhook Signing Secrets — per-subscription secrets, different lifecycle
- Security
- Error Codes —
auth_*