The day the mainnet facilitator stopped settling

A generic error, nine days of blockage and a silent account. How we diagnosed it, what we published, and why the conclusion was not to wait.

The symptom

On 29 August 2026, every attempt to charge on the Stellar main network died at the same point. The hosted facilitator we used answered 200 with a body reading {"invalidReason":"unexpected_verify_error","isValid":false}. Checkout then returned a second 402 and nothing was charged.

Worth saying what did not happen: nobody lost money. The flow stops before settling, which is exactly what the verification step is for. The cost was in the calendar, not in funds.

The A/B test that saves days

The first move was to separate our code from someone else's service. Same code, same USDC asset, same flow, two networks:

  • On testnet: {"isValid":true,"payer":"GAGP5AWB..."}.
  • On mainnet: {"invalidReason":"unexpected_verify_error","isValid":false}.

With that, the hypothesis "our implementation is wrong" is badly weakened, and the job becomes ruling out everything else.

What we ruled out, one by one

  • The mainnet API key. Valid: GET /x402/supported answered 200 and advertised support for stellar:pubnet.
  • The transaction. Decoded by hand: it invoked the right USDC contract, the transfer function, source the buyer, destination the seller, amount in minor units, with the authorisation signed and an expiry ahead of the current ledger.
  • The simulation. Our own simulation against the mainnet RPC passed with no error.
  • The RPC. Three different providers, all three in sync with Horizon, the same error on all three.
  • Our accounts. Buyer and seller funded and with the USDC trustline open, verified on-chain.
  • A transient. Three consecutive attempts, same result.

One reasonable explanation is left: unexpected_verify_error is a generic error, that is, an unhandled exception inside someone else's service.

The likely cause, and the missing data point

Looking outside our own code we found an issue open since 19 August with two independent integrators reporting the same limit. Checking the network, the sponsoring account the service used on mainnet had not sent a single transaction since 10 August. Another account had been active daily since the 13th and appeared to have replaced it, though nobody outside the maintaining team could confirm it.

Our contribution to the thread was a data point nobody had posted: the discovery endpoint itself, /supported, was still advertising the retired account as the mainnet signer. This was not stale documentation: the service was telling every new integrator to use something that had been silent for weeks. On top of that, our failure happened at verification, before settlement, which is where the other two reports failed.

We published all the evidence in the issue the same day, as a third independent integrator. The upstream problem was resolved on 11 September.

Why we did not wait

When a blockage lasts nine days and does not depend on you, the question stops being "how do I fix it" and becomes "why could this stop me at all". The uncomfortable answer is that a store's ability to charge depended on one service hosted by someone else, with no alternative and no service commitment.

We looked at three roads. Waiting was unsustainable. Self-hosting the existing plugin we ruled out on licensing: it is AGPL-3.0, and our project carries no AGPL dependency on any path. The third was to write a minimal facilitator of our own, and that is the one we took.

Minimal is literal: verification and settlement are the ones from the official x402 SDK for Stellar, Apache-2.0, with no cryptography reimplemented. What we wrote is the HTTP surface, the authentication and the guardrails around them.

The rules that got written down

A facilitator sponsors network fees, so whoever runs it pays. Two conditions came out of that, and they are not optional in our code: the account that pays fees is its own account, and verification and settlement refuse a payment whose destination is that same account. The service never holds a merchant's or a buyer's funds: the payment goes from buyer to seller inside the transaction.

And a rule of coexistence: our own facilitator is our default, not a requirement of the protocol. A store can point at any facilitator, the hosted one included, with an environment variable. That is what keeps us from being an intermediary.

On 2 September, with our own facilitator in production, we settled the first real mainnet purchase. Along the way a second problem showed up that nobody had reported: the fixed inclusion fee the client used fell below the network minimum. That is fixed too.