You curl a strange URL your agent just discovered. The response is HTTP 402. Most clients treat that as an error and move on. Smart ones — the ones EVIDIQ is built for — read it like a menu, sign what's offered, and try again. Here's exactly what happens on that second try, byte by byte.
The Wire Protocol, Byte by Byte
An x402 pay-per-call MCP server is an HTTP endpoint — like EVIDIQ's — that returns HTTP 402 Payment Required when a tool call needs payment. The 402 carries an accepts[] array describing price, asset, network, and recipient. The client signs an EIP-3009 transferWithAuthorization off-chain and retries the request with a payment header; the server settles and runs the call.
EVIDIQ's MCP endpoint lives at https://evidiq.dev/mcp. Three tools hang off it today: how_to_install (free), get_evidiq_skill (free, returns the open skill manifest), and verify_agent (paid, returns a 0–100 trust score). The first two don't trigger a 402. The third one always does, because the third one is the product.
The protocol is short. Here's the round trip in six steps:
- Client sends
POST https://evidiq.dev/mcpwith a JSON-RPC body, e.g.{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"verify_agent","arguments":{...}}}. - Server returns
HTTP 402 Payment Requiredwith a body shaped like an x402 v2 challenge. - Client parses
accepts[], picks an offer it can pay for (in our case the only option — exact, USDT0 on X Layer), and asks the user's wallet to signtransferWithAuthorization. - Wallet returns a signature. Client wraps it into the
X-PAYMENTheader (base64-encoded JSON, per x402 v2 spec). - Client re-sends the original
POSTwithX-PAYMENTattached. - Server verifies the signature, broadcasts the settlement transaction on X Layer, waits for inclusion, then runs
verify_agentand returns the trust report in the JSON-RPCresult.
Step 6 is where most implementations collapse into a black box. We made it observable: the response carries the settlement tx hash, and the report itself is hashed and anchored on 0G Storage. Anyone can re-fetch the evidence and re-hash it — we'll get to that in a moment.
Worth noting: x402 v2 changed how the amount is carried. In v1 you saw maxAmountRequired. In v2 each entry in accepts[] has its own amount. If you're still writing a v1 client, the byte you'll trip on is the missing amount field — and you'll see the server reject the header with a 400 before it ever tries to settle.
Reading an accepts[] Object

The 402 body is the part most integrators skim. Don't. It's the contract. A real accepts[] entry from our server looks like this:
{
"scheme": "exact",
"network": "x-layer",
"amount": "100000",
"asset": "0x...USDT0-contract-address...",
"payTo": "0xEVIDIQ...settler-address...",
"maxTimeoutSeconds": 60,
"extra": {
"name": "USDT0",
"version": "1"
}
}
Let's walk it field by field, because we've debugged every one of these the hard way.
scheme — "exact" means a fixed price, paid in full before the call runs. The other scheme in the x402 spec is "upto", where the server can charge whatever the call ended up costing. EVIDIQ uses exact because pricing a trust check is bounded — we know the cost before we run it.
network — "x-layer" is the CAIP-2-style identifier for OKX's X Layer chain. If your client only matches "base" or "polygon", you'll silently skip this offer and the user will see "no payment method available." Wire your matcher to accept the chain ID, not a hardcoded string.
amount — atomic units. 100000 here is 0.10 USDT0 because USDT0 has 6 decimals. Misread this as 100000 whole tokens and you'll attempt a hundred-thousand-dollar payment. Always multiply by the asset's decimals from extra if you're displaying it to a user.
asset — the ERC-20 contract address. Don't substitute the native gas token. The settle transaction is an ERC-20 transferWithAuthorization, not a msg.value transfer. Native X Layer OKB will not satisfy this challenge.
payTo — where the funds land. On EVIDIQ this is a settler address we control. It is not a multisig holding customer money; it is a hot key whose only job is to settle and forward. We never hold balances on behalf of users, and the settler cannot authorize refunds — that is your responsibility, with your escrow of choice, if you need one.
maxTimeoutSeconds — the window in which the signed authorization is still valid. Sixty seconds is what we ship. If your client takes longer to construct and broadcast the signed payload, the server will return 402 again with a fresh nonce.
extra.name and extra.version — these matter for EIP-712 domain separation. USDT0 with version 1 produces one domain separator hash. Get the version wrong and your signature will look valid but fail ecrecover. We have a postmortem on exactly this from week one of the beta.
If you only remember one thing about an x402 pay-per-call MCP server: the accepts[] array is a capability advertisement, not a bill. It tells your client what's possible. The actual bill is what your wallet signs.
Quick recap of the fields worth double-checking before you sign anything:
scheme— must be"exact"for EVIDIQ's fixed-price model.network— match by chain identifier, not a hardcoded string.amount— atomic units; multiply by the asset's decimals before displaying to a user.asset— the ERC-20 contract, never the native gas token.payTo— the settler address; not a custody wallet, not a refund path.
EIP-3009 Without the Cryptography Lecture
The reason EVIDIQ settled on EIP-3009 isn't the cryptography — it's the gas economics. With a normal ERC-20 transfer, someone has to pay gas to move the tokens, and the only party who can do that is the holder of the token. In a pay-per-call MCP server, the holder is a stranger on the other end of an HTTP request, and asking them to hold ETH just to pay you is a non-starter.
transferWithAuthorization flips this. It is a signed message — an EIP-712 typed data blob — that says: "I, the owner of these tokens, authorize address X to move N tokens from me to address Y, but only if the signature hasn't expired and the nonce hasn't been used." The signature is produced off-chain, on the payer's machine, in their wallet. No gas. No on-chain transaction yet.
Then someone else — the settler, in our case EVIDIQ's hot key — takes that signed authorization, builds a transaction that calls transferWithAuthorization on the USDT0 contract, and pays the gas. The contract verifies the signature, checks the nonce and expiry, debits the payer, credits payTo. Done.
This is why our 402 challenge is bearable for an autonomous agent. The agent's wallet signs once locally, hands the signature back to us, and we eat the gas on X Layer. From the agent's perspective it's a single round trip. From ours it's a settlement transaction that costs fractions of a cent.
A concrete bit you'll care about: the EIP-712 typed data has the shape TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce). We generate the nonce server-side and return it inside the accepts[] payload. We generate validBefore as now + maxTimeoutSeconds. Your wallet fills in from, to, value. Don't let it generate its own nonce — you'll collide with the server's and get rejected.
What Happens on a Bad Signature or Late Nonce

Every error here is recoverable. None of them are silent. Here's the map we give integrators when they're wiring a client for the first time.
Expired validBefore. Your wallet signed, then your network blipped, then you retried two minutes later. Server sees the signature, calls the contract, the contract reverts with FiatTokenV2_2: authorization is expired. We surface this as 402 again with a new challenge. The fix is structural: tie signing and retry into a single async unit so the gap is sub-second.
Reused nonce. You retried the same signed payload twice (network flaked after settlement, your retry logic didn't notice). The contract reverts with FiatTokenV2_2: nonce is already used. We return 409 Conflict with {"error":"nonce_consumed"}. Once a nonce is consumed it cannot be re-signed under the same authorization; you must fetch a new 402 and start the dance over.
Wrong domain separator. Your client hardcoded the contract address or version string. The signature recovers to a different address than the one the server expects. We return 402 with an explicit domain_mismatch reason in the body — this is our own extension on top of x402 v2, because the base spec doesn't tell you why the signature is wrong. When you see domain_mismatch, compare extra.name, extra.version, and the asset address against the live values on-chain.
Insufficient balance. Wallet signed; settler broadcasts; contract reverts with an ERC-20 underflow. We return 402 with accepts[] plus an error: "insufficient_funds" field. Your wallet should catch this before signing by reading the balance — but if it doesn't, you'll see it on retry.
Insufficient gas on the settler. This one's on us. Our settler top-up job runs every ten minutes; if X Layer fees spike and we broadcast outside that window, your valid signature will sit in a failed tx. We detect this by polling the receipt and return 503 with {"error":"settler_underfunded","retry_after":30} so your client knows to back off.
We've shipped every one of these paths deliberately. An x402 pay-per-call MCP server that fails silently is worse than one that fails honestly, because your agent will keep spending compute on a call that will never resolve — and you'll burn USDT0 on retries that should have been free.
If you want to see this in your terminal instead of in a spec doc, the EVIDIQ skill is one curl away — it ships with a reference client that handles the 402 dance for you. Or hit the EVIDIQ playground and watch the X-PAYMENT header get built in front of you.
