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, a402 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.
Two different 402s: challenge vs. rejection
A402 occurs in two distinct situations:
- Initial challenge — you called a paid endpoint with no payment attached. This is expected and normal;
@x402/axioshandles it transparently by signing and retrying. - Rejected payment — you (or your client library) attached a payment, and it was rejected. This is the case worth debugging, and it includes an
errorfield.
Known rejection reasons
Troubleshooting execution reverted
Check, in order:
- USDC balance — does your payer wallet hold enough USDC, on Base mainnet, to cover
accepts[].amount(in base units, 6 decimals)? - Correct network — is your USDC on Base mainnet (
eip155:8453), not a testnet or a different chain? Balances do not carry across chains. - Correct asset — are you checking the balance of the exact
assetcontract address in the response (0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913for USDC on Base)?
Important: a rejected retry does not throw
If you’re usingwrapAxiosWithPayment, 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 thePAYMENT-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 apayment-response header, base64-encoded, confirming the on-chain settlement:
x402 docs: Overview · Quickstart · Payment Flow · API Reference · Errors & Troubleshooting · Examples