A request comes in at 03:47 UTC. Some agent, somewhere, decides it needs to verify another agent before handing over a real task. It fires a JSON-RPC call at our endpoint. We respond with HTTP 402 — Payment Required. Five seconds later, the same call comes back, this time carrying a signed payment authorization, and we let it through.
That's the shape of an x402 pay-per-call MCP server in one breath. The rest of this article is the long version — the bytes on the wire, the fields in the challenge, the signature scheme, and the failure modes that will eat your weekend if you don't know them.
If you're wiring up an agent that needs to ask EVIDIQ "is this other agent safe?", here's exactly what crosses the network.
The Wire Protocol, Byte by Byte
An x402 pay-per-call MCP server is an MCP endpoint that gates each paid tool call behind HTTP 402 Payment Required, where the client signs an EIP-3009 transferWithAuthorization off-chain and the server settles it before responding. EVIDIQ runs this pattern at evidiq.dev/mcp — clients discover the price, authorize payment with their wallet, retry once, and receive the trust report plus signed evidence.
Here's the exact sequence we've built at EVIDIQ:
- The client opens a JSON-RPC request:
POST https://evidiq.dev/mcpwith atools/callbody targetingverify_agent. - Our server checks whether the request carries a valid
X-PAYMENTheader. First call? No header. - We return
HTTP 402 Payment Requiredwith a JSON body containing theaccepts[]array — what we'll accept, in what amount, on what network. - The client picks one accept, signs an EIP-3009 authorization, and sends
POST /mcpagain — same body, plus theX-PAYMENTheader holding the signed payload. - Our settler verifies the signature, submits the transfer on X Layer in USDT0, then forwards the original JSON-RPC call to the verifier.
- The response is the trust report — score, recommendation, evidence — and an
X-PAYMENT-RESPONSEheader echoing the settlement tx hash.
Two round trips, one signed message, full settlement. No accounts, no API keys, no monthly invoice. The whole protocol is detailed in our EVIDIQ docs, but let's keep going on the wire itself.
Reading an accepts[] Object
When the 402 lands, the body looks roughly like this:
{
"x402Version": 2,
"accepts": [
{
"scheme": "exact",
"network": "x-layer",
"amount": "10000",
"asset": "USDT0",
"payTo": "0xEvid1q...Settler",
"extra": {
"name": "USDT0",
"version": "1"
}
}
]
}
Every field matters, and getting one wrong is the most common way integrations break. Let's go field by field.
scheme— today it's always"exact". We don't run "upto" or streaming yet. If your client only supports "upto", it'll fail with a clearunsupported_schemeerror.network—"x-layer", the X Layer chain ID, mainnet. Sending a payment on Sepolia or Base won't settle here, and our settler rejects it before any signature work happens.amount— integer string in the smallest unit.10000means 0.01 USDT0 since USDT0 has 6 decimals. Don't pass floats. Don't pass"0.01". The string10000is the only correct answer.asset—"USDT0". We resolve this against our own allowlist. Sending a different asset by mistake gives youunsupported_asset, not a silent fallback.payTo— the EVIDIQ settler address. Don't hard-code an old one from a previous deployment — re-read this field every challenge.extra.name/extra.version— the EIP-712 domain fields for USDT0. These are part of the domain separator. Wrong version? Your signature won't recover.

A useful sanity check: the client should treat the 402 body as authoritative. Don't assume the price from last week is still the price today. We can change amount between requests — and we have, when we adjust pricing.
EIP-3009 Without the Cryptography Lecture
EIP-3009 is transferWithAuthorization. It's a clever piece of paper that lets a wallet sign a message saying "I authorize this exact transfer of this exact amount to this exact address, valid between these two timestamps" — without the wallet ever submitting a transaction itself.
Why does this matter for our x402 pay-per-call MCP server? Because the agent calling us may not have any X Layer gas. It might be a read-only agent, a serverless worker, or a thing living inside another chain's wallet. We don't care. The payer signs. Our settler pays gas.
The shape of the signed message is an EIP-712 typed data payload. The client constructs it locally, signs with the payer's private key, and emits something like:
{
"signature": "0x...",
"from": "0xPayer...",
"to": "0xEvid1q...Settler",
"value": "10000",
"validAfter": "0",
"validBefore": "1735689600",
"nonce": "0xabcd..."
}
Inside our flow:
- The client computes the EIP-712 domain separator from
extra.name,extra.version, the USDT0 contract address, and X Layer's chain ID. - The hash is signed with the payer's key — typically via
eth_signTypedData_v4from MetaMask, Rabby, or a hardware wallet bridge. - The signed blob is base64-encoded and dropped into
X-PAYMENT. - We re-derive the domain separator,
ecrecoverthe signer, check the nonce against the USDT0 contract, and verifyvalidBeforeis still in the future. - Only then do we call
transferWithAuthorizationon the USDT0 contract. The contract itself re-checks the signature and executes the transfer in a single tx — that's the moment gas actually gets spent, and it's on our settler.
Our docs cover the reference implementation in TypeScript and Python at the EVIDIQ Sentinel docs page. The mental model is: the signature is the payment. Once we have a valid one, we can settle whenever we're ready.
We also expose a verification surface via the EVIDIQ Notary docs — once a report is issued, the same signature and nonce can be re-checked publicly. That's what makes the protocol useful for dispute resolution later.
What Happens on a Bad Signature or Late Nonce
Most integrations work on the happy path. Yours won't, at least not the first time. Here are the failure modes we see in production, what triggers them, and what each one looks like to your client.
- Expired
validBefore— the client took too long, or set a window too narrow. The USDT0 contract reverts withFiatTokenV2_2: authorization expired. Our settler surfaces this as402 payment_invalid: expired. Fix: widen the window, or re-fetch the challenge and re-sign. - Reused nonce — same authorization submitted twice, or a stale one from a previous failed retry. Contract reverts with
FiatTokenV2_2: nonce already used. We return402 payment_invalid: nonce_used. Fix: always pull a fresh challenge and sign a new nonce per attempt. - Wrong domain separator —
extra.versionis stale, or the client is computing the separator against a different contract address.ecrecoverreturns the wrong address (or zero). Our pre-check returns402 payment_invalid: bad_signaturebefore we ever hit the chain. Fix: hard-code the latestextra.name/extra.versionfrom the current 402 body. - Wrong network — client signed for Base but our settler is on X Layer. Same as above:
ecrecovermismatches. We return402 payment_invalid: wrong_network. - Amount mismatch — the client signed for
5000but the 402 body said10000. We reject at the settler with402 payment_invalid: amount_mismatchbefore burning gas on a doomed tx.

Each one is recoverable, and none of them are catastrophic. The settler never charges gas for a failed transferWithAuthorization, so a buggy retry loop won't drain a wallet — it just won't get a report. If you want to dive deeper into the operational side, the EVIDIQ Operator docs cover retry policies, idempotency keys, and how to log these failures cleanly.
