Skip to main content
This page explains what happens when an ELUUP paid endpoint receives a request, how the x402 challenge is represented, how payment failures are surfaced, and how successful settlement is confirmed.

HTTP 402 — Payment Required

This is the most important section for client integration — read it carefully even if you’re using @x402/axios (which handles most of this for you automatically).

What a 402 response looks like

Unlike every other status code in this API, a 402 response is generated entirely by the x402 payment middleware — not by ELUUP’s application code — and does not use the { success, data, error } wrapper described in Common Response Structure. Instead, the real information lives in a response header called payment-required, whose value is a base64-encoded JSON object. The raw JSON response body on a 402 is typically empty ({}) — always decode the header, never rely on the body.
Decoded shape:

Two different 402s: challenge vs. rejection

A 402 occurs in two distinct situations:
  1. Initial challenge — you called a paid endpoint with no payment attached. This is expected and normal; @x402/axios handles it transparently by signing and retrying.
  2. Rejected payment — you (or your client library) attached a payment, and it was rejected. This is the case worth debugging, and it includes an error field.

Known rejection reasons

⚠️ Known limitation: the facilitator does not currently return a specific insufficient_funds reason — an underfunded wallet surfaces as the generic execution reverted message above. If you see this error, check your balance first before assuming another cause.

Troubleshooting execution reverted

Check, in order:
  1. USDC balance — does your payer wallet hold enough USDC, on Base mainnet, to cover accepts[].amount (in base units, 6 decimals)?
  2. Correct network — is your USDC on Base mainnet (eip155:8453), not a testnet or a different chain? Balances do not carry across chains.
  3. Correct asset — are you checking the balance of the exact asset contract address in the response (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 for USDC on Base)?

Important: a rejected retry does not throw

If you’re using wrapAxiosWithPayment, be aware of this behavior: on the initial unpaid request, a 402 is handled transparently (signed, retried). But if the retried, paid request is also rejected (e.g. insufficient funds), the wrapper resolves the promise with the final 402 response rather than throwing — it lands in your try block, not your catch block. Do not assume a resolved promise means success. Always check response.status:

Inspecting what your client actually signed

To debug what payment payload your own client submitted, decode the PAYMENT-SIGNATURE request header the same way (base64 → JSON). It shows the authorization.from address, value (amount), and nonce that were signed — useful for confirming the payer address matches the wallet you expect to be funded.

Successful Payment Response Header

Every successful paid request includes a payment-response header, base64-encoded, confirming the on-chain settlement:
Decoded shape:

x402 docs: Overview · Quickstart · Payment Flow · API Reference · Errors & Troubleshooting · Examples