Skip to main content

Error Codes

Errors from the /v1 API return JSON with error, code, fix, docs, and selfHeal fields, plus an X-Request-Id response header.

{
"error": "Human-readable error message",
"code": "error_code",
"fix": "Suggested action to resolve the error",
"docs": "https://docs.vonpay.com/reference/error-codes#error_code",
"selfHeal": {
"retryable": false,
"nextAction": "no_action",
"llmHint": "Machine-readable guidance for SDKs and agents."
}
}

The selfHeal object is always present in the standard envelope and gives SDKs and agents a machine-readable hint about whether the request is retriable and what to do next. A small number of internal responses (for example a streaming connection that closes with a bare 503) fall outside this envelope and carry neither the JSON body nor the X-Request-Id header.

HTTP Status Codes

CodeMeaningCommon Causes
400Bad RequestInvalid request body, missing required fields, validation failure
401UnauthorizedMissing or invalid Authorization: Bearer token
404Not FoundSession ID doesn't exist
409ConflictSession is in the wrong state (e.g., already completed)
410GoneSession has expired (configurable TTL — 30-minute default)
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error

Error Codes Reference

CodeHTTPDescription
auth_missing_bearer401No Authorization: Bearer header provided
auth_invalid_key401API key is malformed or does not exist
auth_key_expired401Key has rotated past its grace window
auth_key_type_forbidden403Publishable key used on a secret-only endpoint, or sandbox/live mode mismatch
auth_merchant_inactive401Merchant account is disabled or suspended
merchant_not_onboarded403Live-key creation is blocked until merchant-onboarding review completes
auth_service_unavailable503Authentication service is temporarily unavailable
session_not_found404Session ID does not exist
session_expired410Session expired (TTL elapsed), or a terminal session with no successful charge — create a new one
session_already_completed409A completion call hit an already-succeeded session — the buyer was charged once; do not retry or create a new session (would double-charge). Read the result via webhook or session retrieve
session_wrong_state409Session is in the wrong state for this operation
session_integrity_error500Internal session state mismatch — contact support
validation_error400Request body failed schema validation
validation_missing_field400A required field is missing from the request body
validation_invalid_amount400Amount is not a positive integer or exceeds maximum
merchant_not_configured422Merchant is missing required configuration (e.g., payment provider credentials)
binder_unavailable409Merchant has no live payment provider configured and is not in sandbox mode — complete onboarding or use a test-mode key
capability_not_supported422The merchant's payment provider does not support the requested operation — check GET /v1/capabilities
rate_limit_exceeded429IP-axis rate limit — retry after the Retry-After interval
rate_limit_exceeded_per_key429Per-API-key rate limit (30 session-creates/min) — contact support if you need a higher ceiling
provider_unavailable502Upstream payment provider is not responding
provider_attestation_failed403Payment provider rejected the session-bound attestation
provider_charge_failed402Card declined or charge rejected by the upstream provider
provider_request_rejected422Payment provider rejected the request as invalid before any money moved (not a decline, not an outage) — fix the offending field and retry
internal_error500Unexpected server error
webhook_missing_signature401Inbound provider webhook is missing its signature header
webhook_invalid_signature401Webhook signature does not match the expected value
webhook_not_configured503Webhook verification secret is not configured on the server
webhook_test_delivery_failed502A synchronous webhook test-send probe could not be delivered — retriable
origin_forbidden403Internal endpoint called from outside the checkout page
transaction_verification_failed403Transaction could not be verified with the payment provider
unsupported_media_type415Content-Type header is missing or not application/json
endpoint_not_implemented501A payment-intent operation is not yet implemented for this merchant's provider (capability gate)
idempotency_replay_incompatible422Idempotency key was reused with a different request body
invalid_transition409Payment intent is in a state that forbids the requested operation (e.g. capture on succeeded)
refund_intent_not_refundable422The parent payment intent is not in a refundable state (refunds require status: "succeeded")
refund_amount_exceeds_remaining422Requested refund amount is greater than the remaining refundable balance
refund_currency_mismatch422Refund currency does not match the parent intent's currency
payment_method_required422This merchant's payment provider requires a vaulted payment method on the request
payment_method_not_found404The payment_method.id does not exist or does not belong to this merchant

This table covers the error codes you are most likely to encounter; the full catalog is larger and continues to grow.

Rate-limit buckets are documented on the Rate Limits page.

Validation Errors (400)

Validation errors include a descriptive message from the schema validator. The error field carries the raw validator output — a JSON array of issue objects, each with the failing field's path and a message:

{
"error": "[\n {\n \"expected\": \"number\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"amount\"\n ],\n \"message\": \"Invalid input: expected number, received string\"\n }\n]",
"code": "validation_error",
"fix": "Ensure 'amount' is a positive integer in minor units (e.g., 1499 for $14.99)",
"docs": "https://docs.vonpay.com/reference/error-codes#validation_error"
}

Common validation issues:

FieldRule
amountMust be a positive integer (1–99,999,999)
currencyMust be exactly 3 characters
countryMust be exactly 2 characters
successUrlMust be HTTPS (localhost exempt in sandbox/test mode)
lineItemsMax 100 items
metadata valuesMax 500 characters each

For POST /v1/sessions, currency is always required. amount is required for payment-mode sessions (the default); a setup-mode (card-on-file) session may omit it.

Debugging

Every /v1 response includes X-Request-Id (success or error). When contacting support, include this ID for fast issue resolution.

X-Request-Id: CezuRjYK_sos

The value is a short URL-safe identifier (mixed-case letters, digits, -, and _) on most routes; webhook-subscription and webhook-event routes return a full UUID instead.


Per-code reference

Each error code emitted in a response body's docs field links to its section below. Each section gives the HTTP status, the cause, and the fix.

auth_missing_bearer

HTTP: 401. The request did not include an Authorization: Bearer <key> header. Add the header with your vp_sk_* or vp_pk_* key. (A legacy vp_key_* key, treated as a secret key, is also accepted.)

auth_invalid_key

HTTP: 401. The API key is malformed, unknown, or has been revoked. Check the prefix (vp_sk_test_, vp_sk_live_, vp_pk_test_, vp_pk_live_) and confirm the key exists in /dashboard/developers/api-keys. If you just rotated, double-check the grace window hasn't expired.

auth_key_expired

HTTP: 401. The key has rotated past its grace window (default 24 hours, configurable per rotation). Distinct from auth_invalid_key so SDKs can detect rotation and fetch a fresh key instead of failing the request. Get a fresh key from /dashboard/developers/api-keys.

auth_key_type_forbidden

HTTP: 403. Primary cause: a publishable key (vp_pk_*) used against a secret-only endpoint like GET /v1/sessions/:id. Also fires on sandbox/live-mode mismatches — for example, a sandbox key attempting to create a live payment session. The fix field on the response tells you exactly what to switch to.

merchant_not_onboarded

HTTP: 403. You tried to create a live-mode API key, but your merchant account has not yet completed onboarding review, so live-key issuance is blocked. Test-mode keys (vp_sk_test_*) remain self-serve at any time via /dashboard/developersCreate sandbox; live keys can only be minted once your account reaches the ready-to-transact (or live) state. If review is still in progress, check /dashboard/settings for your application status and any outstanding requirements; if your application was declined, contact Von Payments support with the details provided in your denial notice. Distinct from auth_merchant_inactive (401), which fires on a previously-approved account that has since been deactivated.

auth_merchant_inactive

HTTP: 401. The merchant account has been disabled or suspended. Check /dashboard for status banners; contact support if unexpected.

auth_service_unavailable

HTTP: 503. The authentication service is temporarily unavailable. This is retriable — the SDK auto-retries with backoff.

session_not_found

HTTP: 404. The session ID does not exist. Sessions are scoped to the merchant; you cannot look up another merchant's session with your key. Confirm the ID was created with the same key mode you're now querying with.

session_expired

HTTP: 410. The session expired (TTL configurable at create — default 30 minutes, range 5 minutes to 7 days), or it reached a terminal state with no successful charge (failed / cancelled). Create a new session. (If the session already succeeded, you get session_already_completed — HTTP 409 — instead; see below.)

session_already_completed

HTTP: 409. A completion call (POST /v1/public/tokens or …/confirm) — or a session retrieve — hit a session that already succeeded. The buyer was charged exactly once and it's recorded. This is a duplicate or late call (a retry, double-submit, or page reload). Do not retry, and do not create a new session for the same purchase — either would charge the buyer again. Treat it as success: read the result from your payment_intent.succeeded / charge.succeeded webhook or GET /v1/public/sessions/:id (status succeeded). This is a distinct 409 Conflict (the session is in a terminal succeeded state) — not session_expired's 410 Gone — so even a status-only client won't "start over" into a re-charge. Branch on the code for the remediation.

session_wrong_state

HTTP: 409. The session is in a state that forbids this operation (e.g. the session already succeeded and cannot be cancelled). Read the response body — the fix field describes the allowed state transitions.

session_integrity_error

HTTP: 500. Internal session state mismatch. Rare; indicates session metadata in the database no longer matches an invariant the runtime expects. Capture the X-Request-Id and contact support — this is not safely retriable without investigation.

validation_error

HTTP: 400. The request body failed schema validation. The response error field contains the validator's output — a JSON array of issue objects, each carrying the path to the bad field (for example path: ["amount"]) and a message such as Invalid input: expected number, received string.

validation_missing_field

HTTP: 400. A required field is missing. See Create a Session for the required fields.

validation_invalid_amount

HTTP: 400. Amount is not a positive integer, is zero, or exceeds the 99,999,999 maximum. Remember: amounts are in minor units1499 = $14.99, not $1,499.

merchant_not_configured

HTTP: 422. The merchant has not completed onboarding for this operation — usually payment-provider credentials are not yet provisioned. Complete boarding via the merchant dashboard, or contact support.

binder_unavailable

HTTP: 409. The merchant has no live payment provider configured and is not in sandbox mode, so the requested operation cannot be routed to a provider. The merchant (not the integrator) must complete onboarding to attach a payment provider. To keep building in the meantime, switch to a sandbox API key (vp_sk_test_*) — test mode runs without a configured live provider. Not retriable as-is.

capability_not_supported

HTTP: 422. The merchant's payment provider does not support the operation you invoked — for example partial refunds, ACH, or network tokens — even though the route exists. Call GET /v1/capabilities for the merchant's supported_operations matrix and gate capability-dependent calls on it. The matrix is provider-dependent; an operation supported on one provider may not be on another, and switching providers requires re-onboarding. Not retriable without a different capability or provider.

rate_limit_exceeded

HTTP: 429. The generic rate-limit code, emitted on the IP-axis limiter (and the global primary limiter). Retry after the Retry-After interval. The SDK auto-retries up to maxRetries times. The per-API-key axis has its own distinct code, rate_limit_exceeded_per_key.

rate_limit_exceeded_per_key

HTTP: 429. Per-API-key bucket exceeded on POST /v1/sessions (30 session creates/min). A single key should not exceed this under normal traffic. If your integration legitimately does, contact support for a ceiling increase. Distinct from rate_limit_exceeded so SDKs can tell them apart.

provider_unavailable

HTTP: 502. Upstream payment provider is not responding. Retriable — the SDK auto-retries with backoff.

provider_attestation_failed

HTTP: 403. The payment provider rejected the session-bound attestation token during the charge step. The most common cause is amount or scope drift between session create and complete — the attested amount or scope no longer matches the session. Fix: verify the session amount matches the attestation payload, or create a new session and re-attest. Not safely retriable without investigation when the cause is a scope / integrity mismatch — capture the X-Request-Id before retrying. The specific provider-native reason (e.g. ATTESTATION_EXPIRED, ATTESTATION_INVALID, ATTESTATION_MERCHANT_MISMATCH) is included in the error message so the buyer can be shown a more specific hint in your UI.

provider_charge_failed

HTTP: 402. The upstream payment provider returned a terminal charge failure — card declined, insufficient funds, fraud-rule block, or a network-side decline (issuer, scheme, or processor). Fix: prompt the buyer to try a different payment method. Retrying the same card/session is unlikely to succeed and may trigger additional issuer-side flags. This is distinct from provider_unavailable (transient infrastructure) and transaction_verification_failed (post-charge reconciliation mismatch).

provider_request_rejected

HTTP: 422. The merchant's payment provider rejected the request as invalid before any money movement was attempted — this is neither a card decline (provider_charge_failed, 402) nor a transient outage (provider_unavailable, 502). It is a permanent request-validation failure: retrying the identical body will fail again. Inspect the fields a provider most commonly constrains beyond our own schema — currency (must be enabled on the merchant's provider account), amount (within the provider's minimum/maximum for that currency), and any statement-descriptor or metadata values (provider length/character limits) — then retry with corrected values. If every charge on this merchant fails this way, the merchant's provider configuration is likely incomplete; capture the X-Request-Id and escalate. Not retriable as-is.

internal_error

HTTP: 500. Unexpected server error. Capture the X-Request-Id and contact support.

webhook_missing_signature

HTTP: 401. An inbound provider webhook arrived without the provider's expected signature header. This error is internal to Von Payments' inbound webhook handling, not something your endpoint emits. (The signature on webhooks Von Payments sends to you is the x-vonpay-signature header — verify it with the SDK's webhooks.verifySignature; see below.)

webhook_invalid_signature

HTTP: 401. Webhook signature does not match the expected HMAC. When verifying webhooks Von Payments delivers to you, HMAC the raw request body (not the parsed JSON), using your endpoint or subscription signing secret (whsec_…) — not your API key. See Webhook Signature Verification.

webhook_not_configured

HTTP: 503. Webhook verification secret is not configured on the Von Payments server side. This is an infra-level misconfiguration, not a merchant-side issue. Capture the X-Request-Id and contact support.

webhook_test_delivery_failed

HTTP: 502. A synchronous webhook test-send probe ran but the delivery could not be completed — a transport error or timeout reaching your subscription endpoint. This is retriable: retry with exponential backoff starting at 3 seconds, and if it persists, confirm the subscription endpoint is reachable. Note the distinct outcome: if your endpoint is reached but returns a non-2xx, that comes back as a 200 with delivered: false and the endpoint's response_statusnot this code.

origin_forbidden

HTTP: 403. The endpoint is internal and only callable from the hosted checkout page; an external caller hit it directly. If you are building a server-side integration, use the public API (e.g. POST /v1/sessions) instead of the internal checkout endpoints.

transaction_verification_failed

HTTP: 403. Transaction could not be verified with the payment processor. Contact support with the X-Request-Id — this is not safely retriable without investigation (a transaction either exists on the processor or it doesn't).

unsupported_media_type

HTTP: 415. The Content-Type header is missing or not application/json. This check is enforced on the browser-facing public endpoints — POST /v1/public/tokens and the public confirm endpoint — so set Content-Type: application/json when calling them. (A trailing ; charset=… parameter is tolerated.)

endpoint_not_implemented

HTTP: 501. The requested payment-intent operation is not yet available for this merchant's payment provider. Capability-gated — read client.capabilities.get() and check supportedOperations before invoking optional operations like paymentIntents.capture, paymentIntents.void, or refunds.create with partial amounts. The capability matrix is provider-dependent; an operation that works on one provider may not work on another. Available in SDK 0.5.0+. Not retriable — the operation will not succeed without a different capability or provider.

idempotency_replay_incompatible

HTTP: 422. The same Idempotency-Key was sent with a different request body. For payment-intent creation, the server compares the new request against the original on amount, currency, and capture_method; if any of those differ, the replay is rejected to prevent silent state corruption. Either retry with the original values or generate a fresh idempotency key for the new request. Available in SDK 0.5.0+.

invalid_transition

HTTP: 409. The payment intent is in a state that forbids the requested operation — e.g., trying to capture an intent that's already succeeded, or void one that's already voided. The error envelope carries current_status (the intent's actual state) + reject_reason — a discriminator the SDK exposes as currentStatus / rejectReason, with values such as already_captured, already_voided, not_authorized, and terminal_state — so the SDK / agent can branch without a follow-up retrieve. Available in SDK 0.6.0+. Not retriable as-is — fix the request (e.g., call refunds.create instead of paymentIntents.void once the intent has captured).

refund_intent_not_refundable

HTTP: 422. The payment intent named in payment_intent is not in a refundable state — refunds require the parent intent to have status: "succeeded". An intent in requires_action / authorized / captured / failed / voided cannot be refunded: call /capture or /void instead, or — if the intent failed — no refund applies because no money moved. Check the parent intent's current status before retrying. Not retryable as-is.


refund_amount_exceeds_remaining

HTTP: 422. The requested refund amount is greater than what's left to refund on this intent. The error envelope carries remaining_refundable (the captured amount minus what's already been refunded) so the SDK / agent can re-request with a valid amount, or you can omit amount to refund the full remaining balance. Fix the input and retry.


refund_currency_mismatch

HTTP: 422. The currency in the refund request differs from the parent intent's currency. Refunds always settle in the original capture currency — either drop the currency field (it defaults to the intent's currency) or supply the matching ISO-4217 code. Fix the input and retry.

payment_method_required

HTTP: 422. This merchant's payment provider requires a previously-vaulted payment method on every charge, but the request omitted one. Use the two-step flow: (1) POST /v1/tokens with the buyer's payment data to vault it, then (2) POST /v1/payment_intents with payment_method: { id: <vp_pmt_*> }. Some providers also support a browser-side confirmation flow (omit payment_method, then confirm in the buyer's browser) — call GET /v1/capabilities to see whether the merchant's current provider supports that path. Not retriable as-is.

payment_method_not_found

HTTP: 404. The payment_method.id is unknown to this merchant. Verify the id was not truncated, that it was created against the same merchant whose API key you're using, and that the test/live mode matches the key prefix. Payment-method ids are scoped to the merchant; you cannot reference another merchant's token. Not retriable as-is — fix the id and retry.


Decline reasons (failure_code)

A declined charge surfaces two ways:

  • Synchronously — the API returns 402 provider_charge_failed.
  • Asynchronously — a charge.failed or payment_intent.failed webhook fires, carrying three fields (all string | null):
    • failure_code — the normalized decline reason to branch on.
    • failure_reason — a human-readable summary to show the buyer.
    • network_decline_code — the raw issuer code (e.g. 05, 51); for logging/analytics only.

Branch on failure_code. It is normalized to a fixed vocabulary of values. The full set and recommended handling:

failure_codeMeaningRetry the same card?Recommended next step
card_declinedGeneric issuer decline, no specific reason given.Unlikely to helpPrompt for a different payment method.
insufficient_fundsThe card lacks available funds.Not nowSuggest a different card; the same card may succeed later.
expired_cardThe card has passed its expiry date.NoAsk the buyer to re-enter a valid card.
incorrect_cvcThe CVC/security code was wrong.MaybeAsk the buyer to re-enter the security code.
incorrect_zipThe billing postal code did not match.MaybeAsk the buyer to re-enter the billing ZIP/postal code.
card_velocity_exceededThe card hit an issuer velocity limit.Not nowSuggest a different card or trying again later.
fraudulentThe issuer or a fraud rule blocked the charge.NoShow a generic decline message — do not reveal the fraud signal. Suggest a different method or contacting the issuer.
stolen_cardThe card was reported stolen.NoShow a generic decline message; suggest a different method.
lost_cardThe card was reported lost.NoShow a generic decline message; suggest a different method.
do_not_honorThe issuer declined without a specific reason.Unlikely to helpPrompt for a different payment method or contacting the issuer.
issuer_unavailableThe issuer could not be reached.YesSafe to retry shortly; if it persists, try a different method.
processing_errorA transient processing failure.YesSafe to retry; if it persists, try a different method.
generic_declineAn unmapped or unrecognized raw decline.Unlikely to helpPrompt for a different payment method.

Show failure_reason to the buyer so they can act on it; keep network_decline_code out of buyer-facing copy. The failure_code vocabulary above is fixed; an unmapped raw code is normalized to generic_decline before it reaches you, so you will always receive one of these values.

Trigger each of these deterministically with the test cards. Note that the 3-D Secure / fraud decline test card surfaces failure_code as fraudulent on the wire.