x402 Settlement Provider Shows Success: Why Did the On-Chain Transaction Still Fail?

 / 
2

The settlement provider tells you "success: true", but the on-chain transaction receipt is red. Do not doubt the block explorer. On-chain failure is failure. That field from the settlement provider only means "I tried to submit this", not "I confirmed that it went through".

A reader came to us last month. He used x402 to call a paid API. Binance's settlement endpoint returned success=true and even gave a transaction hash. He thought everything was fine. Then he checked a block explorer and saw the transaction status was reverted. No money was taken, and no API data was received. He ended up with nothing.

The problem is understanding the x402 settlement return value. In the x402 protocol, the success field has two layers of meaning. If you only look at the first layer, you will fall into this trap.

Core truth: success=true does not equal "on-chain confirmation"

According to Binance's x402 documentation, the /settle endpoint returns three possible cases:

Return combinationActual meaning
success=true, transaction=0x...On-chain settlement is confirmed. This is a final state.
success=false, transaction=0x...The transaction was broadcast but rolled back on-chain. This is a final state. Check errorReason.
success=false, transaction=""It failed before broadcasting. Nothing happened on-chain.

The key footnote is this: on-chain failure is not returned as an HTTP error code. It comes back as HTTP 200 plus success=false. The documentation especially says: always check the success field and errorReason, not just the HTTP status code.

So if you see success=true, the settlement provider has confirmed that the transaction succeeded on-chain. If you see success=false but the transaction field has a hash, the transaction was broadcast but rolled back. No money was taken, but the service may have already been delivered.

Step 1: Confirm which kind of "success" you received

What to do: Check the full data structure returned by the /settle or /verify endpoint, not only the success boolean.

How to do it:

  • Open your proxy call logs and find the settlement endpoint response body.

  • Look for these fields:

    • success: true or false

    • transaction: a hash value or an empty string

    • errorReason: a value or empty

Done when: You confirm which of these cases your response falls into:

Case A: success=true, transaction=0x... → On-chain confirmation succeeded. Money was charged, and the service should have returned the data. If you did not get the data, the problem is on the service side, not settlement.

Case B: success=false, transaction=0x... → The transaction was broadcast but rolled back. No money was charged. However, the settlement provider may have already sent a wrong "payment successful" signal to the upstream service, so you were allowed in without actually paying. If the service side does not check the on-chain state again, you may get the data while no money is actually taken. That is a loss for the service provider, and for you it is a gain. But you should not rely on this error path.

Case C: success=false, transaction="" → It failed before broadcasting, for example invalid signature or insufficient balance. No on-chain action happened.

Step 2: If success=false, transaction=0x..., check the block explorer

This is the most confusing combination: the settlement provider returned a transaction hash, but told you the settlement failed.

What to do: Go to the matching block explorer and check the real status of the transaction.

How to do it:

  • Take the hash from the transaction field.

  • Go to the matching blockchain explorer (Base uses basescan.org, Injective uses the Injective explorer, Stacks uses stacks.co).

  • Look at the transaction status field.

Done when:

  • If the explorer shows Success: The settlement provider's success=false is a false report. There is a real case on GitHub: a sponsor relay broadcast 4 transactions, all returned errors, but 3 of them actually confirmed on-chain. This is a misjudgment caused by polling the status too early. The settlement provider checked the status before the transaction was confirmed.

  • If the explorer shows Reverted: The settlement provider's success=false is correct. The transaction really failed.

High-risk reminder: x402 has an architecture-level problem. HTTP is synchronous, but blockchain is asynchronous. The settlement provider is a middle party and cannot guarantee atomicity. There is a two-way risk: "service delivered but payment not confirmed" or "payment arrived but service not delivered". This means the case where the settlement provider returns success=false but the transaction actually succeeded on-chain is real.

Step 3: Check whether the settlement provider code is missing a key check

If you run your own open-source x402 settlement server (such as the Rust version x402-axum), note a known issue: the settlement_to_header function does not check the SettleResponse.success field when serializing the settlement response. Even if the settlement provider returns success: false, paygate still lets the request through and returns the protected resource.

The official TypeScript implementation explicitly checks !settleResponse.success and returns 402 when it fails. If the implementation you use is missing this check, you can see "settlement provider shows failure, but the service still lets you through". Your balance does not drop, but you get the data. That is like getting the resource for free. For the service provider, it is a vulnerability.

What to do: If you run your own x402 server, check the logic after settlement.

How to do it:

  • Locate settlement_to_header or the equivalent serialization function.

  • Confirm that before serialization there is a check like if (!settlement.success) return 402.

Done when: The code has an explicit failure branch. It does not put the settlement response into the header unchanged and then allow access.

Verification checklist

Run one full test:

  1. Use a wallet with insufficient balance to start an x402 payment.

  2. Watch the settlement provider's response. It should be success=false, and transaction may be empty or a hash, depending on how it rolled back.

  3. Check whether your server returns 402 instead of 200 and allowing access.

  4. Check whether the wallet balance changed. If the balance was insufficient, no money should be taken.

Verification channels:

  • The success field and errorReason field in the settlement response.

  • The transaction status on the block explorer.

  • Server logs that have records related to "Settlement failed".

If you use a third-party settlement provider, trust its return value. success=true means real success. success=false means real failure. You do not need to guess. But do not forget to double-check the block explorer, especially when the settlement provider gives you a hash but tells you it failed.