XAgent Blog

Home Blog Market Launch Store

When an agent payment is neither settled nor failed

x402 added a non-terminal settlement error on August 17. It exists because a broadcast transaction whose receipt you cannot read is not a failure — and retrying it can charge twice.

By XAgent Team · 2026-08-19

The worst state in a payment system is not failure. It is not knowing. On August 17 x402 shipped a spec change for exactly that state, and it is the only change to specs/x402-specification-v2.md this week.

The new error code is settlement_pending, and the spec defines it like this: "The settlement transaction was broadcast but its confirmation could not be established (e.g. a node/RPC error or timeout while waiting for the receipt). Facilitators MAY return this non-terminal code — the transaction may still confirm on chain. A SettleResponse with this errorReason MUST carry a non-empty transaction (the broadcast hash) and network so the caller can reconcile on chain before deciding whether to retry."

A hash you cannot confirm is not a failure

Before this, an RPC timeout while waiting for a receipt produced a terminal error. The caller saw a failed settlement and did the obvious thing, which was the wrong thing.

PR #3083 states the reasoning without hedging: "Treat every receipt-wait exception after a valid broadcast hash as settlement_pending. The exception type cannot prove that the transaction will not confirm; preserving the hash is safer than reporting a terminal failure that could lead callers to retry a completed payment."

That middle clause is the whole argument. The exception type cannot prove that the transaction will not confirm. A timeout tells you your connection to a node ended. It tells you nothing about whether a transaction already in the mempool gets mined thirty seconds later. Software that maps "I stopped waiting" onto "it failed" is asserting something it did not observe.

The spec makes the hash mandatory rather than advisory. In the SettleResponse field table, transaction is now described as: "Blockchain transaction hash (empty string if no transaction was broadcast; MUST be non-empty when errorReason is settlement_pending)". An implementation that returns settlement_pending without a hash has given the caller a state it can neither retry nor resolve.

The failure is the retry, not the timeout

Consider what a merchant-side integration does with a terminal settlement error. It marks the order unpaid, and either releases the goods on a different path or asks the agent to pay again. The agent, holding a signed authorization, pays again. Then the first transaction confirms.

Now there are two settled payments for one delivery, and the reconciliation is manual because nothing in the flow recorded that the first attempt had a hash. This is not an exotic scenario. RPC providers time out under load, and load is correlated across everyone using the same provider — so it happens to many integrations at once, on the busiest days.

settlement_pending does not prevent the double payment. It gives you the one thing you need to prevent it yourself: a hash to check before you decide. The correct handling is to treat it as an open question, poll the chain, and only then choose. It is worth writing that path before you need it, because the day you need it is the day everything else is also failing.

The code covers "the exact, upto, and batch-settlement EVM schemes (v2 only)", which includes the usage-metered flow we looked at yesterday — where the settlement transaction goes out after the resource has already been delivered. An unresolved settlement there is a delivered order with no confirmed payment.

The same ambiguity, inverted, on Solana

The facilitator documentation describes the mirror-image problem on the other chain, and reading the two together is instructive.

"On Solana, a race condition can occur when the same payment transaction is submitted to a facilitator's /settle endpoint multiple times before the first submission is confirmed onchain. Because Solana's RPC returns "success" for duplicate submissions (the network deduplicates at the consensus level), the facilitator may return a successful settlement response for each call. A malicious client could exploit this to access multiple resources while only paying once."

So: on EVM the failure mode is a settlement you cannot confirm succeeded. On Solana it is a settlement the RPC confirms too many times. One risks charging the buyer twice; the other risks delivering to them twice for one payment.

The Solana mitigation is a cache: "a short-lived, in-memory cache that detects and rejects duplicate settlement attempts for the same transaction payload. The cache requires no external storage and entries are automatically evicted after 120 seconds (approximately twice the Solana blockhash lifetime)."

In-memory and 120 seconds are both worth reading carefully. A facilitator running more than one process does not share that cache between them, and a window longer than the blockhash lifetime is not covered. It is a sensible default, not a distributed guarantee.

If you run a facilitator on serverless, set the bound

The documentation added the next day carries the most immediately actionable line of the week: "Facilitators deployed behind a platform request deadline (serverless functions, gateway timeouts) should bound the receipt wait below that deadline. If the process is killed mid-wait, the caller receives a 5xx with no transaction hash instead of settlement_pending with a hash to reconcile against."

The defaults do not fit that deployment. Per the docs, "The default is 180_000 ms (3 minutes), matching viem's own default. In Python, pass confirmation_timeout_seconds to FacilitatorWeb3Signer (default 120)." Three minutes is longer than most serverless request limits.

const evmSigner = toFacilitatorEvmSigner(walletClient, {
  confirmationTimeoutMs: 25_000, // set a few seconds below your platform deadline
});

Without it, the platform kills the process mid-wait and the new error code never reaches anyone. You get a 5xx, no hash, and the exact ambiguity the spec change was written to remove.

What's next

Two of this week's three x402 changes are about who holds uncertainty and who pays for it. That is a good sign for the protocol — it means the interesting problems have moved from "can an agent pay" to "what happens when the payment is in an unknown state", which is where real payment systems spend their engineering.

It is also the part that does not live in a spec. Reconciling a pending hash, deciding whether to release goods against an unconfirmed settlement, and absorbing a facilitator's bad day are operational obligations, and they land on whoever is closest to the merchant. That is the layer we build: protocols will keep producing new intermediate states, and someone has to turn them into an answer a merchant can act on. If you are integrating settlement now, handle the pending case first — it is the one that costs money when you get it wrong.

Keep reading

  • x402 can now charge for what an agent actually used
  • x402 clients now refuse to sign payments over one dollar
  • Agent checkout just grew credit terms. Who authorised them?