We run DeskCrew, a helpdesk where AI agents can use support tools with no account and no API key — they pay per action in USDC over x402, the HTTP-402 payment standard. An agent calls a tool, gets a 402 Payment Required with an exact quote, signs a USDC authorization, retries, done. Five cents, no signup.
Most x402 services use a hosted facilitator to verify and settle payments. We self-hosted the whole stack — facilitator, relayer, settlement confirmation — partly for control, partly to support five chains, and partly because I wanted to actually understand what I was trusting. Here's what bit us on the way, so it doesn't bite you.
x402 in sixty seconds
- Client calls your paid endpoint with no payment → you respond
402with a quote: price in atomic USDC, your receiving address, network, a timeout. - Client signs an EIP-3009
transferWithAuthorization— a gasless, off-chain signature authorizing a USDC transfer from their wallet to yours, bound to a unique nonce. - Client retries with the signed payload in an
X-PAYMENTheader. - Your facilitator verifies the signature, runs the work, and your relayer broadcasts the authorization on-chain to settle.
Simple. The gotchas live in steps 2–4.
Gotcha 1: "nonce consumed" is not "you got paid"
This is the one that matters. Our first settlement-recovery path checked authorizationState(from, nonce) on the USDC contract — if the nonce was used, we marked the payment settled and the revenue earned.
The payer controls that nonce. EIP-3009 lets the authorizer burn their own nonce: they can call cancelAuthorization, or spend the same nonce in a self-transfer, racing your relayer. Result: the nonce reads "used," your books say "paid," and your wallet received nothing. An adversarial agent could get your tool's output for free, at scale.
The fix is to confirm the money movement, not the nonce state. On settlement recovery we now require finding the AuthorizationUsed(authorizer, nonce) event and verifying the USDC Transfer log adjacent to it in the same transaction — checking it moved from the payer, to our receiving wallet, with value >= the quoted amount. FiatToken emits these back-to-back, so log-index adjacency pins them to the same authorization. Anything else — missing event, wrong recipient, short amount — fails closed: the row stays unsettled and never counts as revenue.
Rule of thumb: any state the payer can influence is not proof of payment. Only the Transfer to your address is.
Gotcha 2: exactly-once work, payment-after-success
Two invariants pull against each other:
- Never take money for work that failed → settle only after the tool succeeds.
- Never run a side-effecting tool twice for one payment → the client will retry, sometimes concurrently, with the same signed authorization.
Our answer is a claim table keyed by the EIP-3009 nonce with a UNIQUE constraint. The first request to insert the pending row wins the race and runs the tool; everyone else carrying the same nonce is a retry and gets refused. If the tool succeeds but settlement fails (RPC hiccup, gas spike), we stamp the row consumed-but-unsettled — the row is never deleted, so the same authorization can never re-run the action — and a reconcile job retries the on-chain settlement later, using the Gotcha-1 confirmation before it ever promotes the row to revenue.
The ordering that emerges: claim the nonce → validate input → run the tool → settle → record. Validation before payment matters more than it looks — taking five cents and then returning "invalid input" is a great way to make an agent developer never come back.
Gotcha 3: multi-chain is not copy-paste
"USDC on five chains" sounds like a for-loop. It isn't:
- Every chain has a different USDC contract address, and some have bridged-vs-native variants — quote the wrong asset and signatures verify against a token you don't accept.
- Solana has no EIP-3009. The whole authorization model changes: a fee-payer co-signs, the flow is transaction-based rather than signature-based, and your "facilitator" logic is effectively a second implementation, not a port.
- Advertise networks honestly in your quotes: a client that picks Sei because your 402 offered it, only to find your relayer has no gas there, burns real goodwill.
If you're starting out: ship Base only, add chains when someone asks.
Gotcha 4: discovery is table stakes, not demand
We serve /.well-known/x402, per-workspace manifests, and we're in the MCP registry. Do all of that — it's cheap and it's how agents can find you. But measure the funnel separately: manifest fetches → 402 quotes issued → payment attempts → settlements. Fetches without quotes are curiosity. Quotes without attempts are checkout friction. Only settled transfers from wallets you don't control are demand. We log all four stages and split "our own test wallets" from strangers, so we can't fool ourselves.
Kick the tires (paid, but we'll pay you back)
The door is live on mainnet:
- Terms:
https://deskcrew.io/.well-known/x402 - Quickstart with a copy-paste 402 flow: deskcrew.io/agents
- Cheapest calls are $0.02; the fully account-free one is
draft_support_replyat $0.05 — send raw customer text, get a support reply draft back. Payment settles only after the tool succeeds.
If you've got an x402-capable agent: the first 20 external wallets to make a successful paid call get 5× the call price back — reply with your tx hash wherever you found this post. Worst case, you're out two cents and got a story.
Questions about the facilitator internals welcome — happy to go deeper on any of the four gotchas.
Top comments (0)