Common failures and fixes
The failures builders hit most often, what each looks like in a run, and what
fixes it. Every FAIL in a run already carries its own Fix line; this page
collects them by check, with the reasoning. Where a failure was met in a real
implementation, it links the write-up.
The unpaid request does not get a 402 (X402-01)
- 200: the route serves without payment. The payment middleware is not in front of it, or protects a different path.
- 401 or 403: the endpoint asks for authentication instead of payment. x402 clients act only on 402.
- 404 or 405: usually the wrong path or method. An endpoint that computes
something often takes POST:
wasit test --method POST --body '{...}'(MCP:methodandbody).
There is no payment header (X402-02)
x402 v2 carries the challenge base64-encoded in the PAYMENT-REQUIRED response
header. A service that puts an x402 v1 challenge in the response body fails
here, and the result says it found one; its terms are still checked. The
exact scheme on Stellar is defined for v2 only. This is the divergence Wasit
has met in a real implementation
(conformance findings, class 1).
The header does not decode (X402-03)
The header value must be the base64 of the JSON PaymentRequired object. Raw
JSON, or a value cut short, does not decode.
A field is missing or has the wrong type (X402-04)
Every option in accepts needs every field its x402Version requires: for v2,
scheme, network, amount, asset, payTo and maxTimeoutSeconds. The
usual slips in a challenge built by hand:
maxTimeoutSecondsleft out. The official x402 server SDK sets it to 300 when you do not.amountsent as a number. It is a string of the token's smallest units, such as"10000".- The v1 price name
maxAmountRequiredin a v2 challenge. v2 calls itamount.
A challenge built with the official server SDK carries every field.
The network id is rejected (X402-05)
x402 v2 names each network with a CAIP-2 id, namespace:reference:
| Network | CAIP-2 id |
|---|---|
| Stellar testnet | stellar:testnet |
| Stellar mainnet | stellar:pubnet |
| Base Sepolia | eip155:84532 |
| Base | eip155:8453 |
| BNB Smart Chain testnet | eip155:97 |
| BNB Smart Chain | eip155:56 |
| Solana devnet | solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 |
| Solana mainnet | solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp |
The Stellar ids come from the exact scheme on Stellar, the Base and Solana ids
are the x402 v2 specification's own examples (§11.1), and the BNB Smart Chain
ids are its EIP-155 chain ids. The usual slips:
- A v1 name such as
stellar-testnetorbase-sepolia(conformance findings, class 2). - A hex chain id.
eth_chainIdreturns hex; the CAIP-2 reference is base 10, soeip155:0x14a34iseip155:84532. stellar:mainnet. Stellar's mainnet isstellar:pubnet.- A Solana cluster name. The reference is the first 32 characters of the cluster's genesis hash.
A valid payment is refused (X402-06)
Wasit built the payment with the official client, for exactly the advertised terms, and the target refused it. The target's own log of the facilitator's verify or settle response says why.
The paid response has no PAYMENT-RESPONSE (X402-06)
After settling, the server returns the facilitator's settle result,
base64-encoded, in PAYMENT-RESPONSE; it names the settlement transaction.
Without it a client cannot tell whether the payment settled, so X402-06
fails. The official x402 middleware sends it. A server that serves without
settling looks exactly like this, and wasit serve --mode no-settle is one,
for testing an agent against it.
The settlement does not match (X402-06)
The transaction PAYMENT-RESPONSE names must have succeeded and moved exactly
amount of asset from this run's payer to payTo, in one transfer. A server
that cites another transaction fails, even a real and successful one
(0.6.0 verification run).
A forged signature is accepted (X402-07)
The target served a payment whose authorization signature was corrupted: it decoded the payment without verifying it. Pass every payment to the facilitator's verify step and refuse it when verification fails.
The same payment is accepted twice (X402-08)
A target that serves a payment it has already been paid with sold the resource twice
for one payment. Each authorization is single-use; the facilitator's verify refuses one
whose nonce is spent, so verify every payment before serving and never serve a payload
twice. A server that offers the payment-identifier extension may answer a repeat with
its cached response, and X402-08 is skipped for it.
A payment for less than the price is accepted (X402-09)
The signature was valid; only the amount was short. A server that checks only the signature, or skips the facilitator's verify, sells for less than its price. Hold the signed amount to the advertised one before serving: on Stellar it must be exact.
An expired payment is accepted (X402-10)
An authorization past its window (validBefore on EVM, the auth entry's expiration
ledger on Stellar, the recent blockhash on Solana) can never settle, so a target that serves it serves for nothing.
Verify with the facilitator before serving; it refuses expired authorizations.
A charge to a muxed recipient is refused (MPP-01)
A charge server on @stellar/mpp 0.7.1 configured with a muxed (M...)
recipient cannot verify the payment and refuses it. Since CAP-67, a transfer
to a muxed address reports its amount in a form the SDK does not read. Use a
G... recipient until the SDK handles it
(Finding 5, reported as
stellar/stellar-mpp-sdk#89).
Wasit reads both forms.
A stale or replayed voucher is accepted, or refused with 500 (MPP-11, MPP-12, MPP-14)
The server must refuse with 402: a commitment that does not exceed the stored
cumulative or does not cover the price, a second credential for a challenge
already used, and an accepted commitment presented under a new challenge. The
@stellar/mpp channel server enforces all three. Refusals reported as HTTP 500
instead of 402 were seen on the SDK's main branch between its mppx 0.10.1
upgrade and the fix in #83, never in a release
(Finding 4).
No verdict on the channel checks (ERROR (setup))
MPP-11, MPP-12 and MPP-14 each need one correctly advancing commitment
accepted first. When three attempts are refused, the run reports no verdict
rather than a failure, because a channel another payer is advancing at the same
time looks the same from the client. Re-run against a channel nothing else is
paying through.
The payment checks cannot pay
On Stellar, the payment checks need a testnet payer holding testnet USDC.
wasit wallet create --role x402 --fund generates the key and funds it with
testnet XLM, and wasit wallet fund --role x402 --asset usdc adds the USDC
trustline; the balance itself needs one visit to https://faucet.circle.com,
since there is no scriptable USDC faucet for Stellar (see the CLI guide's
wallet setup).
On Base Sepolia (--network eip155:84532), the payer is EVM_PRIVATE_KEY, a
raw 0x private key. It needs Base Sepolia USDC from the same faucet and no
ETH at all: the facilitator pays the gas. A Stellar secret there, or any
malformed key, is reported at PREFLIGHT before anything is sent.
On Solana devnet (--network solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1), the payer is
SVM_PRIVATE_KEY, the base58 encoding of the 64-byte keypair, as wallets export it. It
needs devnet USDC of the SDK's default mint (4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU)
and no SOL: the facilitator signs as fee payer. The payee's USDC token account must
already exist, since the payment's transaction does not create it. A keypair whose public
half does not belong to its seed, or any malformed key, is reported at PREFLIGHT.