AIOT Payment x402 is in public beta. Install the skill for your AI agent.
GuidesFor DevelopersError Handling

Error Handling

Every failure the SDK throws is a subclass of AIOTError. Each subclass carries a machine-readable code, an HTTP statusCode, and a semantic name so you can branch on instanceof instead of parsing strings.

The hierarchy

AIOTError                       (base, statusCode = 500 by default)
β”œβ”€β”€ CheckoutExpiredError        (410, code: CHECKOUT_EXPIRED)
β”œβ”€β”€ PaymentFailedError          (402, code: PAYMENT_FAILED)
β”œβ”€β”€ ShopifyAPIError             (502, code: SHOPIFY_API_ERROR)
β”œβ”€β”€ PriceConversionError        (503, code: PRICE_CONVERSION_ERROR)
└── X402APIError                (facilitator status, code: X402_API_ERROR)

When to expect each error

NameTypeDescription
CheckoutExpiredError410The session TTL elapsed. Create a new checkout; do not retry.
PaymentFailedError402Verification or settlement failed. Inspect err.reason; the user may retry.
ShopifyAPIError502Shopify admin GraphQL returned user errors. Inspect err.errors for the field[] + message.
PriceConversionError503Fiat-to-USDC conversion could not reach the oracle. Usually transient; retry with backoff.
X402APIErrorvariableFacilitator returned a non-2xx. err.responseBody has the raw payload.
AIOTErrorvariableBase class; catches everything the SDK throws deliberately.

Canonical catch patterns

Completing a payment

import {
  AIOTError,
  CheckoutExpiredError,
  PaymentFailedError
} from '@aiot/shopify-x402'
 
try {
  await client.completePayment(sessionId, { payment, eip7702Auth })
} catch (err) {
  if (err instanceof CheckoutExpiredError) {
    return res.status(410).json({ error: 'Session expired, please retry' })
  }
  if (err instanceof PaymentFailedError) {
    return res.status(402).json({
      error: err.message,
      reason: err.reason,
      transaction: err.transaction
    })
  }
  if (err instanceof AIOTError) {
    return res.status(err.statusCode).json({
      error: err.message,
      code: err.code
    })
  }
  throw err // let your outer error handler log it
}

HTTP status mapping

The Express middleware does this mapping for you via a shared handleError helper. If you roll your own framework integration, the statusCode-on-error-class pattern makes it trivial to preserve:

res.status(err instanceof AIOTError ? err.statusCode : 500).json({
  error: err.message,
  code: err instanceof AIOTError ? err.code : 'INTERNAL_ERROR'
})

Logging vs. responding

Warning

Don’t log PaymentFailedError at error level β€” it’s a user-visible state, not a system failure. Log it at info or warn, along with the session id and reason, so you can triage without drowning on-call during normal refund activity.

Rules of thumb:

  • PaymentFailedError, CheckoutExpiredError β†’ info/warn, include session id
  • ShopifyAPIError, X402APIError β†’ error, include operation + payload
  • PriceConversionError β†’ warn + retry with backoff
  • Anything else β†’ error + pager