Skip to main content

Embedded Fields — Error handling

Embedded Fields' error surface is VoraMirrorError — 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 VoraMirrorError is a separate union.

v1.x alias note. FrameError is the same class — the SDK shipped under that name through v1.1.x and the symbol is preserved as a deprecated alias through v1.x. New code should import VoraMirrorError / isVoraMirrorError / VoraMirrorErrorCode. Existing imports of FrameError keep working unchanged. err instanceof VoraMirrorError and err instanceof FrameError return identical results for any instance — they're two named bindings of one class.


Detection

import { VoraMirrorError, isVoraMirrorError } from "@vonpay/vora-js";

try {
const elements = vora.elements.create();
const card = elements.create("card", {});
card.mount("#card-element");
const result = await elements.submit();
} catch (err) {
if (isVoraMirrorError(err)) {
// err.code is one of the VoraMirrorErrorCode union below
console.error(err.code, err.message);
} else {
// Network errors, programming bugs (TypeError on construction, etc.)
throw err;
}
}

Always use isVoraMirrorError()instanceof VoraMirrorError can fail across module boundaries (bundle-splitting / multiple package copies).


Code union

type VoraMirrorErrorCode =
| "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_3ds_required"
| "frame_payment_declined"
| "frame_wallet_unavailable"
| "frame_wallet_domain_unverified"
| "frame_wallet_rendered_by_card_element"
| "frame_style_invalid"
| "frame_method_not_supported_for_session"
| "frame_rate_limited";

The wire-level code strings (frame_*) are stable contract and are NOT renamed — your log shippers, alerting rules, and grep patterns over historical data keep working unchanged. The type name VoraMirrorErrorCode is the canonical TypeScript identifier; FrameErrorCode remains as a deprecated alias for v1.x callers.

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)

CodeWhen it firesRecovery
frame_insecure_contextnew Vora(...) outside HTTPS / localhostServe the page over HTTPS in production. localhost is allowed for development.
frame_session_not_foundSession ID is unknown to VORAVerify 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_elementelements.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 capabilitiesThe 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_method_not_supported_for_sessionThe deprecated card.tokenize() single-field path was called against a single-embed binder session"card.tokenize() is deprecated. Use vora.elements.create() + collection.create('card') + collection.submit() to charge and save. Your server configuration is fine." This is an integration-time wrong-method condition (single-embed binder) — not a server bug, not high-severity, and should not page. Switch to the canonical vora.elements.create() + collection.create('card') + collection.submit() path.
frame_style_invalidStyle option contains a key outside the allowlistThe thrown error's property field names the offending key. Drop it or alias to a supported VoraFieldStyle key.

Transient errors (retry / refresh)

CodeWhen it firesRecovery
frame_session_expiredThe session's TTL elapsed before tokenizeCreate a fresh session on your server, then vora.sessions.retrieve(newId). Sessions default to 30 minutes.
frame_session_not_readyAn action method (tokenize, confirmPaymentIntent, handleAction) was called before ready === trueWait for ready === true before exposing your submit button. In React, gate the button render on ready.
frame_binder_load_failedThe active processor's CDN script failed to loadInspect 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_timeoutBuyer didn't complete 3DS challenge within challengeTimeout (default 5 min / 300_000 ms)Surface a "the bank's authentication timed out" message; let the buyer retry.
frame_rate_limitedThe publishable-key endpoints (sessions.retrieve, binder-load, the embed-token poll) are per-IP rate-limited (HTTP 429) and the caller exceeded the ceiling — usually rapid reloads or a tight retry loopBack off and retry after a short delay; honor the Retry-After header if present. Do not retry immediately in a loop — that deepens the throttle. Not a CSP/binder/session problem.

Buyer-action errors (surface a message)

CodeWhen it firesRecovery
frame_field_validation_failedCard number / expiry / CVC failed VORA's pre-tokenize validationAlready surfaced inline by the iframe. Disable the submit button until change event reports complete: true.
frame_tokenization_failedThe adapter rejected the card before any charge (decline, network error at the adapter) — a genuine pre-charge rejectionShow 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 adapter messages to buyers.

Charge-and-save carve-out. On the embedded charge-and-save flow, a successful guest / no-buyer submit charges the card once and returns no token by design — the result resolves as { charged: true }, NOT frame_tokenization_failed. A missing token is therefore not, on its own, a failure. Branch on result.charged before treating an absent token as frame_tokenization_failed, and never re-charge after a charged result. frame_tokenization_failed fires only on a genuine pre-charge adapter rejection (no charge occurred); the charge-only success path is a settlement you confirm via the webhook. | 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. | | frame_3ds_required | The issuer requires a 3DS challenge that hasn't been completed for this submit | Re-run the submit so the harmonized 3DS modal renders, or forward the server-issued client_confirm block back into the SDK to drive the challenge. See 3D Secure. | | frame_payment_declined | The charge was attempted and the issuer declined it (charge-and-save / charge-only flows where the embed charges at submit) | Show a generic "your payment was declined — please try a different card" message and re-enable the submit button. The decline reason is logged server-side; do not leak issuer messages to buyers. |

Wallet errors (Apple Pay / Google Pay)

Wallets render inside the card mount automatically when eligible — see Elements reference → Apple Pay & Google Pay. When an eligibility gate fails, the wallet button simply doesn't appear and the buyer completes checkout via the card field. The codes below surface on the change event so analytics layers can record wallet-coverage gaps; neither blocks the card flow.

CodeWhen it firesRecovery
frame_wallet_unavailableBuyer's device or browser doesn't support the wallet (e.g. Apple Pay on a non-Safari, non-WebKit browser; Google Pay on a buyer without a saved card).No action required — the wallet button doesn't render and checkout continues via the card field. Optional: log the event so you can measure wallet-availability coverage across your buyers.
frame_wallet_domain_unverifiedYour serving domain isn't registered with the wallet network for this merchant account. The wallet button doesn't appear; the card field still works.New merchants are auto-registered on first session-create. If this persists, the merchant's domain needs to be registered manually via the merchant dashboard.
frame_wallet_rendered_by_card_elementYou tried to mount a standalone wallet element, but the active binder renders Apple Pay / Google Pay inside the card mount instead.No separate wallet element is needed — wallets appear inside the card mount automatically when eligible. Remove the standalone wallet element and rely on the card mount. See Elements reference → Apple Pay & Google Pay.

frame_binder_load_failed — sub-hints

When this fires, inspect error.hint for the specific failure:

catch (err) {
if (isVoraMirrorError(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

VoraMirrorError 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