All posts
x402July 16, 2026·9 min read

Inside EVIDIQ's x402 Pay-Per-Call MCP Server: The Wire Protocol, Up Close

Inside EVIDIQ's x402 Pay-Per-Call MCP Server: The Wire Protocol, Up Close

Your agent just wants to verify another agent's identity before paying it. It fires off a tool call to EVIDIQ's MCP server, fully expecting an answer — and gets back an HTTP 402. No result, no friendly error message that says "give me money," just an accepts[] array sitting in the JSON body like a price tag the server expects the client to honor. Most tutorials on the x402 pattern stop right there with a hand-wave about "payment headers." We built EVIDIQ's x402 pay-per-call MCP server, so let's walk the actual bytes.

The Wire Protocol, Byte by Byte

An x402 pay-per-call MCP server is an HTTP endpoint that returns a 402 Payment Required challenge when a tool call needs payment, accepts EIP-3009 signed authorizations on retry, and only then executes the request. EVIDIQ's MCP server at evidiq.dev/mcp exposes exactly this pattern for its verify_agent tool, settled in USDT0 on X Layer.

Here's the literal sequence our verify_agent flow runs through:

  1. Client POSTs a JSON-RPC tools/call to https://evidiq.dev/mcp with the tool name verify_agent and the target agent's identity as arguments.
  2. Because the request carries no payment proof, EVIDIQ's server responds with HTTP 402 and a body shaped per x402 v2 — the accepts[] array.
  3. The client parses accepts[], picks the asset and network it can actually pay on (USDT0 on X Layer for us, but the spec allows several), and signs a transferWithAuthorization payload off-chain.
  4. The client retries the exact same JSON-RPC call, this time adding an X-PAYMENT header that carries the base64-encoded signed payload.
  5. The server validates the signature, hands it to our settler contract, and only on a clean on-chain settlement does it run the actual trust evaluation — then returns the report.

That's the whole loop. The trick — and the part most implementations fumble — is that step 4 has to be byte-for-byte identical to step 1 in the JSON-RPC body. Otherwise the server can't replay the call against the payment it just verified. The 402 challenge itself carries no request-specific data; the binding between payment and call is reconstructed server-side from the header and the body hash.

A few details worth knowing before you start:

  • HTTP 402 is real, not a 200-wrapped custom error. Middleware and proxies that strip "weird" status codes will silently break your client.
  • accepts[] is plural because x402 v2 lets a server advertise multiple rails in parallel (different chains, different tokens). The client picks one.
  • The X-PAYMENT header is the v2 transport. Older drafts used different names; if you're copy-pasting from a 2026-era tutorial, double-check.
  • The settle step is what gates execution. We never run the trust evaluation before the on-chain receipt confirms. That ordering is non-negotiable for us.

If you want to see the protocol on the wire before writing code, the playground at evidiq.dev/playground shows every step including the raw signed payload.

Reading an accepts[] Object

The accepts[] array is the contract between server and client, and getting any field wrong burns time. Here's what EVIDIQ returns for a verify_agent call on USDT0 / X Layer, with each field explained.

{
  "x402Version": 2,
  "accepts": [
    {
      "scheme": "exact",
      "network": "x-layer",
      "amount": "100000",
      "asset": "0x...USDT0",
      "payTo": "0x...EVIDIQsettler",
      "extra": {
        "name": "USDT0",
        "version": "1"
      }
    }
  ]
}

Field by field:

  • scheme — payment scheme. "exact" means pay exactly the quoted amount. x402 also defines "upto" (variable metering), which we don't use yet.
  • network — the chain slug. "x-layer" is X Layer mainnet. v2 uses simplified slugs; if your code expects CAIP-2 IDs (eip155:196), normalize first.
  • amount — smallest unit. USDT0 has 6 decimals, so "100000" is 0.1 USDT. Passing "0.1" gets you rejected.
  • asset — the ERC-20 contract address. Don't hardcode it; read it from the challenge so a future change doesn't break your client.
  • payTo — destination address. Not EVIDIQ's hot wallet directly — it's the per-call settlement address tied to the verification request.
  • extra.name and extra.version — required for the EIP-712 DOMAIN_SEPARATOR. Hardcoding name = "USDT" when we send "USDT0" invalidates every signature. We'll see this exact failure in the next section.

The most common mistakes integrators hit, in order:

  • Treating amount as a human-readable float.
  • Hardcoding asset or payTo instead of reading from the challenge.
  • Forgetting to validate payTo against a policy ("am I about to pay an address I trust?").
  • Ignoring extra entirely because it looks optional.

EVIDIQ blog illustration 1

EIP-3009 Without the Cryptography Lecture

EIP-3009 is a token standard that defines two off-chain signed messages: transferWithAuthorization and receiveWithAuthorization. The "authorization" part is the trick — instead of the payer calling a contract function (which needs gas and a live nonce), the payer signs a structured EIP-712 message saying "I authorize the contract to move N tokens from me to address X, valid until time T, with a unique nonce Nn." Anyone holding that signature can submit it to the contract later. The contract verifies the signature against ecrecover, checks the nonce hasn't been used, and executes the transfer — and crucially, the submitter pays the gas.

For our x402 pay-per-call MCP server this matters because:

  1. The agent making the call doesn't need to hold any native gas token on X Layer. It only needs the USDT0 balance.
  2. EVIDIQ's settler submits the authorization, pays gas, and only after the token actually moves do we run the verification.
  3. The nonce is bound to the signature — replaying the same authorization a second time fails the contract's authorizationState check, so it's safe to log the signed payload.

Concretely, the X-PAYMENT header carries a JSON document with the EIP-712 type hash, the message struct (from, to, value, validAfter, validBefore, nonce), the signature (v, r, s), and a client identifier. On our side:

  • We reconstruct the EIP-712 digest using extra.name, extra.version, chainId, and verifyingContract from the accepts[] we returned.
  • We run ecrecover(digest, v, r, s) and confirm it equals the from field.
  • We call transferWithAuthorization(...) on the USDT0 contract and wait for the receipt.
  • If the receipt succeeds, we run the trust evaluation and return the report.

If the receipt reverts, EVIDIQ returns 402 again with a more specific error in the body. That's the debug loop — bad payment, fix, retry — and it's why we put structured errors in the response rather than opaque 500s. Honest engineering over optimistic 200s.

What Happens on a Bad Signature or Late Nonce

We've shipped this for over a year in 2026 and these are the failure modes that actually come up. Each one produces a distinct error code you can match against in your client.

  1. Expired validBefore — The signed message includes a unix timestamp after which the authorization is void. If the client signs with validBefore = now + 30s and X Layer is congested, by the time our settler submits, the contract reverts with FiatTokenV2_2: authorization expired. We surface this as HTTP 402 with body {"error": "authorization_expired", "validBefore": 1234567890}.

  2. Reused nonce — Each transferWithAuthorization carries a 32-byte nonce. Once used, the contract's authorizationState[from][nonce] flips to true and any later submit reverts. We return {"error": "nonce_already_used"}. Most often this is a client retrying the same call after a network blip without re-signing.

  3. Wrong domain separator — The single biggest time sink for integrators. The EIP-712 domain is (name, version, chainId, verifyingContract). If a client hardcodes name = "USDT" but our accepts[].extra.name is "USDT0", the digest is wrong and ecrecover returns the zero address. The contract then reverts on from != address(0). We return {"error": "invalid_signature", "hint": "check domain separator"}.

  4. Insufficient balance — USDT0's transferWithAuthorization pulls directly; no allowance dance. But if the payer's balance is below amount, the underlying transfer reverts with a generic ERC-20 underflow. We catch this and return {"error": "insufficient_balance"}.

  5. Chain mismatch — The signed chainId in EIP-712 doesn't match the network our settler is actually submitting to. We return {"error": "wrong_chain", "expected": "x-layer", "signed": "<other>"}.

  6. Replay with mutated body — Client re-signs but changes a field of the JSON-RPC call. The server detects the hash mismatch and returns {"error": "payment_call_mismatch"}. Don't suppress this; it's there to prevent paying for one thing and asking the server to do another.

For each case, EVIDIQ's response includes the specific field the integrator needs to fix. Giving the client a precise token rather than a bare "payment failed" cuts debug time by an order of magnitude — and it's why we have a small army of structured error codes instead of one catch-all.

EVIDIQ blog illustration 2

Frequently Asked Questions

The agent skill at EVIDIQ is open source under MIT. The MCP server's how_to_install and get_evidiq_skill tools are free — they're how you bootstrap an integration. The verify_agent tool is paid per call via the x402 pattern described above. There's no subscription, no API key to manage, no monthly minimum — you pay cents per verification, settled on-chain in USDT0, with no EVIDIQ account required.

Give your agent the trust skill:

curl -s https://evidiq.dev/skill.md
E

EVIDIQ Team

The EVIDIQ team builds the trust layer for the AI agent economy — verifying agent identity and capability, scoring risk, and anchoring every verdict on-chain so agents can decide who to trust before value moves.

More from EVIDIQ Team
How EVIDIQ's x402 Pay-Per-Call MCP Server Actually Works — EVIDIQ