Skip to content
DexterDocs
x402 SDK / Errors and Retries
Sections

Errors and Retries

A lost response can follow a successful payment. Before retrying, determine whether the previous attempt dispatched a payment authorization.

Error Interfaces

APIFailure interface
payAndFetchA PayResult with ok: false, reason, and optional detail.
useX402Payment().fetchA rejected promise plus the hook's error and status state.
createBudgetAccount().fetchA rejected promise; budget checks use X402Error.
Tab operationsRejected promises, including typed tab errors and HTTP failures.

These contracts refer to @dexterai/x402 version 6.0.0. Unexpected application errors still need normal exception handling.

Read a PayResult

read-result.ts
import type { PayResult } from '@dexterai/x402/client';

export async function readResult(result: PayResult) {
  if (!result.ok) {
    return { state: 'failed', reason: result.reason, detail: result.detail };
  }

  if (!result.paid) {
    return {
      state: 'unpaid-response',
      status: result.response.status,
      body: await result.response.text(),
    };
  }

  return {
    state: result.response ? 'paid-response' : 'paid-without-response',
    amountAtomic: result.amountPaid,
    network: result.network.caip2,
    transaction: result.txSignature,
    status: result.response?.status,
    body: result.response ? await result.response.text() : undefined,
  };
}

An unpaid response can be a successful free request or an HTTP error. Check its status. On a paid result, the transaction signature is optional; retain it when present.

Retry Decisions

reasonMeaningNext action
unsupported_networkThe client cannot use the offered network.Supply a compatible wallet or choose another endpoint.
insufficient_fundsThe wallet cannot cover the payment.Check the charged asset and network before funding.
no_payment_optionsNo supported payment option was recognized.Inspect the endpoint's 402 challenge.
budget_exceededThe amount exceeds the configured cap.Review the price and cap. No payment was dispatched by this attempt.
timeoutThe SDK classified a timeout before payment dispatch.Address the timeout and retry.
merchant_rejectedThe server reports rejecting the payment.Inspect detail and the payment terms before retrying.
settlement_failedThe server reports a settlement failure.Reconcile with the seller or facilitator before another attempt.
payment_unconfirmedAuthorization was sent; settlement could not be confirmed.Reconcile first. A fresh attempt can authorize another charge.
errorUnclassified failure.Preserve detail and investigate; payment state is unknown.

For an uncertain attempt, retain the endpoint, request time, wallet, amount, and any request ID or transaction identifier. Give that evidence to the seller or inspect the corresponding chain transaction. HTTP status alone does not establish that funds stayed in the wallet.

Timeouts and Missing Responses

payAndFetch separates a pre-payment timeout (timeoutMs, default 15000) from the wait after dispatch (responseTimeoutMs, default 120000). A post-dispatch timeout can return payment_unconfirmed, or a paid result with response: undefined if settlement was confirmed.

Treat response: undefined on a paid result as a delivery problem. Use the transaction evidence to request recovery from the seller. Paying again creates a separate payment attempt.

A reason: 'timeout' is a result classification, not a guarantee that the entire function finishes within timeoutMs. Apply application deadlines to the initial fetch and handle aborts without assuming they cancel a payment already submitted.

React and Budget Errors

Catch the promise rejection in your event handler or worker. The React hook records an error for display; reset() clears that UI state and transaction fields. It does not cancel or reverse an earlier payment.

The PayResult.reason table above belongs to payAndFetch. Do not assume the same strings appear on every X402Error. In particular, inspect the wrapper's error code and dispatch context before adding an automatic retry loop.

Tab Errors

ClassFieldsHandling
SessionScopeExceededErrorreasonStop spending when the cap, expiry, or counterparty check fails.
TabClosedErrorchannelIdStop using the closed handle.
UnsupportedNetworkErrornetworkUse a network supported by the tab adapter.
LiveSessionExistsErrordetailsInspect and resolve the existing session before opening another for that seller.
HistoricalV1MigrationRequiredErrorsurface, codeReplace the historical grant with an owner-approved V2 session.
tab-error.ts
import {
  LiveSessionExistsError,
  SessionScopeExceededError,
  TabClosedError,
} from '@dexterai/x402/tab';

export function explainTabError(error: unknown): string {
  if (error instanceof SessionScopeExceededError) return error.reason;
  if (error instanceof TabClosedError) return `Tab closed: ${error.channelId}`;
  if (error instanceof LiveSessionExistsError) {
    return `Existing session frontier: ${error.details.frontierAtomic}`;
  }
  return error instanceof Error ? error.message : 'Unknown tab error';
}

Tab operations can also throw ordinary errors for rejected HTTP requests, expired sessions, or transport failures. Preserve the original error when classification falls through.

onLiveSession: 'replace' authorizes replacement of an existing session. Resolve its signed but unsettled vouchers first: replacement can invalidate vouchers beyond the on-chain settled or crystallized amount. A grant-backed tab's close() settles without revoking its session; the wallet owner must revoke it separately. See Node.js Tabs.

Last Updated:

On this page