VORA Mirror — Error handling
VORA Mirror's error surface is FrameError — a typed exception with a stable code, a human-readable message, and optional sub-hints. This page covers the error union, when each fires, and recommended recovery patterns.
For server-side API errors (e.g. /v1/payment_intents 4xx responses), see Reference → Error codes. Server-side errors share the self-healing envelope shape with structured code, fix, docs, and selfHeal fields; client-side FrameError is a separate union.
Detection
import { FrameError, isFrameError } from "@vonpay/vora-js";
try {
await card.tokenize();
} catch (err) {
if (isFrameError(err)) {
// err.code is one of the FrameErrorCode union below
console.error(err.code, err.message);
} else {
// Network errors, programming bugs (TypeError on construction, etc.)
throw err;
}
}
Always use isFrameError() — instanceof FrameError can fail across module boundaries (bundle-splitting / multiple package copies).
Code union
type FrameErrorCode =
| "frame_insecure_context"
| "frame_session_not_found"
| "frame_session_expired"
| "frame_session_not_ready"
| "frame_binder_load_failed"
| "frame_unsupported_element"
| "frame_field_validation_failed"
| "frame_tokenization_failed"
| "frame_3ds_challenge_failed"
| "frame_3ds_challenge_timeout"
| "frame_wallet_unavailable"
| "frame_wallet_domain_unverified"
| "frame_style_invalid";
Each code has a distinct merchant-facing recovery path. Codes are NEVER collapsed when their recovery differs.
Codes by recovery family
Configuration / setup errors (fix the integration)
| Code | When it fires | Recovery |
|---|---|---|
frame_insecure_context | new VORA(...) outside HTTPS / localhost | Serve the page over HTTPS in production. localhost is allowed for development. |
frame_session_not_found | Session ID is unknown to VORA | Verify the merchant's vp_sk_* is for the same merchant whose session you're loading. Check test/live mode parity (publishable key prefix vs session ID prefix must match). |
frame_unsupported_element | elements.create(type, ...) called for an element the active session's binder doesn't support — or payment-method-picker's methods all filter out against per-binder capabilities | The active binder declared not_available (or monolith, for elements whose surface the binder renders natively — e.g. a processor that renders its own payment-method picker as part of an all-in-one embed). Swap the element type, route the merchant to a binder that supports it, or — for picker-on-monolith — remove the element from your elements.create() list and rely on the binder's native UI. The error message identifies which binder and which element. |
frame_style_invalid | Style option contains a key outside the allowlist | The thrown error's property field names the offending key. Drop it or alias to a supported VORAFieldStyle key. |
Transient errors (retry / refresh)
| Code | When it fires | Recovery |
|---|---|---|
frame_session_expired | The session's TTL elapsed before tokenize | Create a fresh session on your server, then vora.sessions.retrieve(newId). Sessions default to 30 minutes. |
frame_session_not_ready | An action method (tokenize, confirmPaymentIntent, handleAction) was called before ready === true | Wait for ready === true before exposing your submit button. In React, gate the button render on ready. |
frame_binder_load_failed | The active processor's CDN script failed to load | Inspect the hint field — network_error / csp_blocked / script_blocked / timeout. Most commonly fixed by adding the processor's domain to your CSP script-src; see Reference → Security. |
frame_3ds_challenge_timeout | Buyer didn't complete 3DS challenge within challengeTimeout (default 10 min) | Surface a "the bank's authentication timed out" message; let the buyer retry. |
Buyer-action errors (surface a message)
| Code | When it fires | Recovery |
|---|---|---|
frame_field_validation_failed | Card number / expiry / CVC failed VORA's pre-tokenize validation | Already surfaced inline by the iframe. Disable the submit button until change event reports complete: true. |
frame_tokenization_failed | Binder rejected the card (decline, network error at the binder) | Show a generic "your card couldn't be authorized" message and suggest a different payment method. The full reason is logged server-side; do not leak vendor messages to buyers. |
frame_3ds_challenge_failed | Buyer failed 3DS challenge (wrong code / cancelled) | Show "authentication failed — please try again or use a different card" and re-enable the submit button. |
Wallet errors (Apple Pay / Google Pay — coming soon)
| Code | When it fires | Recovery |
|---|---|---|
frame_wallet_unavailable | Buyer's device / browser doesn't support the wallet (e.g. Apple Pay on a non-Safari browser) | Don't render the wallet button; fall back to the card field. |
frame_wallet_domain_unverified | The merchant hasn't completed domain verification for the wallet on this domain | Surface "wallet payments aren't enabled on this site yet" to the merchant; the buyer-facing checkout should hide the button. The merchant resolves this in their dashboard. |
frame_binder_load_failed — sub-hints
When this fires, inspect error.hint for the specific failure:
catch (err) {
if (isFrameError(err) && err.code === "frame_binder_load_failed") {
switch (err.hint) {
case "network_error":
case "timeout":
// Transient; suggest retry
break;
case "csp_blocked":
case "script_blocked":
// Configuration; merchant needs to update CSP / disable adblockers
break;
}
}
}
CSP requirements
VORA's iframe-mount model loads the active processor's tokenization library from a processor-owned CDN. Your CSP needs to allow VORA's own CDN plus your active processor's domain.
Always required:
script-src 'self' js.vonpay.com;
Processor-specific domains: the specific hostname depends on how your merchant account is provisioned. After your account is set up, the active processor's CSP allowlist is provided as part of go-live setup (or available from your dashboard's integration details page). If you need it before then, contact support.
If your CSP rejects any of these, you'll see frame_binder_load_failed with hint: "csp_blocked" (when the failure is detectable) or hint: "script_blocked" (when the script tag insertion was blocked outright).
Server-side errors vs client-side errors
FrameError is client-side (thrown by @vonpay/vora-js / @vonpay/vora-react). 4xx / 5xx responses from /v1/sessions, /v1/payment_intents, etc. (your server's calls) are server-side and use the self-healing error envelope:
{
"error": "Authentication required",
"code": "auth_missing_bearer_publishable",
"fix": "Include an Authorization: Bearer <your-publishable-key> header (vp_pk_*)",
"docs": "https://docs.vonpay.com/reference/security#authentication",
"selfHeal": {
"retryable": false,
"nextAction": "fix_request",
"llmHint": "…",
"actions": [
{ "type": "check_format", "field": "Authorization", "expected_pattern": "^Bearer vp_pk_(live|test)_[a-z0-9]+$" }
]
}
}
Public endpoints (/v1/public/sessions/:id, /v1/public/binder-load, /v1/public/tokens) accept publishable keys (vp_pk_*) ONLY. Secret keys (vp_sk_*) are rejected with 403 auth_key_type_forbidden. Secret keys must never reach the browser; if you're testing from a server-side harness, use /v1/sessions/:id and /v1/tokens (the secret-key paths) instead.
The *_publishable variant auth error codes (auth_missing_bearer_publishable, auth_invalid_key_publishable, auth_key_expired_publishable) on these routes carry self-heal envelopes that steer to publishable keys exclusively.
See Reference → Error codes for the full server-side code catalog.
What's next
- Quickstart — end-to-end walkthrough
- Elements reference — per-element behavior
- React wrapper —
useVORA()error patterns - Reference → Error codes — server-side code catalog