Skip to main content

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 returns 403 merchant_not_onboarded with a fix string 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 distinct fix message asking you to contact support@vonpay.com. See Error Codes → merchant_not_onboarded.

Key types at a glance

KeyPrefixWhere to use itRotation
Test secret keyvp_sk_test_Server-side code, test mode onlyGrace window (default 24h)
Live secret keyvp_sk_live_Server-side code, production onlyGrace window (default 24h)
Test publishable keyvp_pk_test_Browser-exposed client code, test mode onlyGrace window (default 24h)
Live publishable keyvp_pk_live_Browser-exposed client code, production onlyGrace window (default 24h)
Session secretss_test_, ss_live_Issued with your keys; not used by your integrationRotated independently
The session secret is not part of any documented flow

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-in vora-hosted.js widget and create sessions — POST /v1/sessions is 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 with auth_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:

    RouteMethodWhat 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_unavailable regardless 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/browser is 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
t0Click Rotate in /dashboard/developers/api-keys. New key is created; old key enters grace.
t0UI shows the raw value of the new key once. Copy it to your secret manager immediately.
t0 → t0+24hBoth keys accepted. Deploy the new key across all your services during this window.
t0+24hOld 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:

  1. 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.
  2. Create a fresh key.
  3. Rotate deployed services to the fresh key.
  4. 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_expired once grace_ends_at passes.
  • Revokeis_active = false, immediate rejection with auth_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.