{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "blog.shveik.dev",
  "home_page_url": "https://blog.shveik.dev/",
  "feed_url": "https://blog.shveik.dev/feed.json",
  "description": "Notes from a small team that crafts quality software: what we build, what we run in production and what we learn along the way.",
  "language": "en",
  "authors": [
    {
      "name": "shveik.dev",
      "url": "https://shveik.dev"
    }
  ],
  "items": [
    {
      "id": "https://blog.shveik.dev/x402-vs-mpp/",
      "url": "https://blog.shveik.dev/x402-vs-mpp/",
      "title": "x402 vs MPP: what we learned building both",
      "summary": "What changed when we settled x402 and MPP in one handler, and how the payment traffic on our pay-per-call services split between the two paths.",
      "content_html": "<article>\n<header>\n<nav class=\"crumbs\" aria-label=\"Breadcrumb\">\n<ol>\n<li><a rel=\"noopener\" href=\"https://shveik.dev\">Home</a></li>\n<li><a href=\"https://blog.shveik.dev/\">Blog</a></li>\n<li><a href=\"https://blog.shveik.dev/x402-vs-mpp/\" aria-current=\"page\">x402 vs MPP: what we learned building both</a></li>\n</ol>\n</nav>\n<p class=\"meta\">Published <time datetime=\"2026-10-06\">2026-10-06</time> · 6 min read</p>\n<h1>x402 vs MPP: what we learned building both</h1>\n</header>\n<aside class=\"tldr\">\n<p><strong>TL;DR.</strong> We settle x402 and MPP in one handler. In our traffic about 96 of every 100 payments used x402 and about 4 used MPP. Per challenge issued, both converted at about the same rate, so the gap is mostly in who arrives, not in who pays.</p>\n</aside>\n<nav class=\"toc\" aria-label=\"Contents\"><ol><li><a href=\"https://blog.shveik.dev/x402-vs-mpp/#what-x402-and-mpp-are\">What x402 and MPP are</a></li><li><a href=\"https://blog.shveik.dev/x402-vs-mpp/#what-we-built\">What we built</a></li><li><a href=\"https://blog.shveik.dev/x402-vs-mpp/#things-that-bit-us\">Things that bit us</a></li><li><a href=\"https://blog.shveik.dev/x402-vs-mpp/#what-the-traffic-says\">What the traffic says</a></li><li><a href=\"https://blog.shveik.dev/x402-vs-mpp/#why-mpp-lags\">Why MPP lags</a></li><li><a href=\"https://blog.shveik.dev/x402-vs-mpp/#what-we-would-do-as-a-seller\">What we would do as a seller</a></li><li><a href=\"https://blog.shveik.dev/x402-vs-mpp/#caveats\">Caveats</a></li><li><a href=\"https://blog.shveik.dev/x402-vs-mpp/#sources\">Sources</a></li></ol></nav>\n<div class=\"prose\">\n<p>We run small pay-per-call services that accept both x402 and MPP. We settle them in one handler, because both credentials are an EIP-3009 <code>transferWithAuthorization</code> signature on USDC. The headers, the nonce rule, and the binding differ. The signature does not. This post covers what the two protocols have in common, what bit us, and what our traffic says about them.</p>\n<h2 id=\"what-x402-and-mpp-are\">What x402 and MPP are</h2>\n<p>Both build on HTTP 402. The client requests a resource, the server answers 402 with a challenge, the client sends a payment credential, and the server settles it and then returns the resource. The specs are the <a href=\"https://x402.org\" rel=\"noopener\">x402 spec</a> and the <a href=\"https://mpp.dev\" rel=\"noopener\">MPP spec</a>.</p>\n<p>x402 puts the challenge in <code>PAYMENT-REQUIRED</code> as base64 JSON, the credential comes back in <code>PAYMENT-SIGNATURE</code>, and success is <code>PAYMENT-RESPONSE</code>.</p>\n<p>MPP puts the challenge in <code>WWW-Authenticate: Payment</code>, the credential is <code>Authorization: Payment</code> plus a base64url payload, and success is <code>Payment-Receipt</code>.</p>\n<p>A <a href=\"https://workos.com/blog/x402-vs-stripe-mpp-how-to-choose-payment-infrastructure-for-ai-agents-and-mcp-tools-in-2026\" rel=\"noopener\">WorkOS comparison</a> says x402 is stateless per request, and that MPP adds sessions: pre-authorized spend, streamed payments, and batch settlement. It also says MPP has been submitted to the IETF, that MPP carries a Stripe and Tempo dependency, and that MPP can express an x402-style one-shot payment.</p>\n<p>A <a href=\"https://www.spotedcrypto.com/trm-labs-x402-ai-agent-payments-report-2026/\" rel=\"noopener\">TRM Labs analysis, as reported</a>, found only roughly 0.6 to 7.5 percent of screened x402 volume plausibly agentic. <a href=\"https://www.mexc.com/news/901995\" rel=\"noopener\">Artemis, as reported</a>, estimated about half of x402 transactions were artificial. <a href=\"https://www.fintechweekly.com/magazine/articles/blackrock-machine-native-economy-ai-agents-stablecoins-2026\" rel=\"noopener\">Coverage of BlackRock's machine-native economy paper</a> says the paper discussed x402 and stablecoins. <a href=\"https://fortune.com/2026/09/30/cloudflare-tool-businesses-ai-agents-stablecoins\" rel=\"noopener\">Fortune</a> reported that Cloudflare announced tooling to charge agents in USDC.</p>\n<h2 id=\"what-we-built\">What we built</h2>\n<p>One handler settles both. Both credentials are an EIP-3009 <code>transferWithAuthorization</code> signature on USDC.</p>\n<figure class=\"diagram\"><svg xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-labelledby=\"one-handler-title one-handler-desc\" viewBox=\"0 0 560 656\" width=\"560\" height=\"656\">\n<title id=\"one-handler-title\">One handler, two protocols</title>\n<desc id=\"one-handler-desc\">A client on the left, one handler in the middle, and USDC settlement on the right. The handler dispatches on the Authorization header. x402 uses PAYMENT-REQUIRED, PAYMENT-SIGNATURE, and PAYMENT-RESPONSE. MPP uses WWW-Authenticate: Payment, Authorization: Payment, and Payment-Receipt. The 402 challenge goes back to the client and the credential comes in. Delivery waits until settlement is confirmed. Ambiguous means not delivered.</desc>\n<rect x=\"8\" y=\"8\" width=\"156\" height=\"120\" fill=\"var(--tint)\" stroke=\"var(--muted)\" stroke-width=\"1.5\"/>\n<text x=\"86\" y=\"76\" text-anchor=\"middle\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"24\">client</text>\n<polygon points=\"182,68 168,60 168,76\" fill=\"var(--accent)\"/>\n<line x1=\"164\" y1=\"68\" x2=\"176\" y2=\"68\" stroke=\"var(--accent)\" stroke-width=\"2\"/>\n<rect x=\"184\" y=\"8\" width=\"168\" height=\"120\" fill=\"var(--tint)\" stroke=\"var(--muted)\" stroke-width=\"1.5\"/>\n<text x=\"268\" y=\"48\" text-anchor=\"middle\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"20\">one handler</text>\n<text x=\"268\" y=\"74\" text-anchor=\"middle\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"20\">verify, settle,</text>\n<text x=\"268\" y=\"100\" text-anchor=\"middle\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"20\">then deliver</text>\n<polygon points=\"370,68 356,60 356,76\" fill=\"var(--accent)\"/>\n<line x1=\"352\" y1=\"68\" x2=\"364\" y2=\"68\" stroke=\"var(--accent)\" stroke-width=\"2\"/>\n<rect x=\"372\" y=\"8\" width=\"180\" height=\"120\" fill=\"var(--tint)\" stroke=\"var(--muted)\" stroke-width=\"1.5\"/>\n<text x=\"462\" y=\"46\" text-anchor=\"middle\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"22\">USDC</text>\n<text x=\"462\" y=\"74\" text-anchor=\"middle\" fill=\"var(--fg)\" font-family=\"var(--mono)\" font-size=\"20\">transferWith</text>\n<text x=\"462\" y=\"98\" text-anchor=\"middle\" fill=\"var(--fg)\" font-family=\"var(--mono)\" font-size=\"20\">Authorization</text>\n<rect x=\"8\" y=\"140\" width=\"544\" height=\"44\" fill=\"var(--tint)\" stroke=\"var(--line)\"/>\n<text x=\"280\" y=\"168\" text-anchor=\"middle\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"20\">dispatch on the Authorization header</text>\n<rect x=\"8\" y=\"196\" width=\"544\" height=\"180\" fill=\"var(--bg)\" stroke=\"var(--accent)\" stroke-width=\"1.5\"/>\n<text x=\"24\" y=\"224\" fill=\"var(--accent)\" font-family=\"var(--sans)\" font-size=\"22\">x402</text>\n<polygon points=\"24,256 40,248 40,264\" fill=\"var(--accent)\"/>\n<line x1=\"40\" y1=\"256\" x2=\"56\" y2=\"256\" stroke=\"var(--accent)\" stroke-width=\"2\"/>\n<text x=\"64\" y=\"262\" fill=\"var(--muted)\" font-family=\"var(--sans)\" font-size=\"20\">402 challenge</text>\n<text x=\"210\" y=\"262\" fill=\"var(--fg)\" font-family=\"var(--mono)\" font-size=\"20\">PAYMENT-REQUIRED</text>\n<line x1=\"24\" y1=\"292\" x2=\"40\" y2=\"292\" stroke=\"var(--accent)\" stroke-width=\"2\"/>\n<polygon points=\"56,292 40,284 40,300\" fill=\"var(--accent)\"/>\n<text x=\"64\" y=\"298\" fill=\"var(--muted)\" font-family=\"var(--sans)\" font-size=\"20\">credential</text>\n<text x=\"210\" y=\"298\" fill=\"var(--fg)\" font-family=\"var(--mono)\" font-size=\"20\">PAYMENT-SIGNATURE</text>\n<polygon points=\"24,328 40,320 40,336\" fill=\"var(--accent)\"/>\n<line x1=\"40\" y1=\"328\" x2=\"56\" y2=\"328\" stroke=\"var(--accent)\" stroke-width=\"2\"/>\n<text x=\"64\" y=\"334\" fill=\"var(--muted)\" font-family=\"var(--sans)\" font-size=\"20\">success</text>\n<text x=\"210\" y=\"334\" fill=\"var(--fg)\" font-family=\"var(--mono)\" font-size=\"20\">PAYMENT-RESPONSE</text>\n<rect x=\"8\" y=\"388\" width=\"544\" height=\"180\" fill=\"var(--bg)\" stroke=\"var(--muted)\" stroke-width=\"1.5\"/>\n<text x=\"24\" y=\"416\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"22\">MPP</text>\n<polygon points=\"24,448 40,440 40,456\" fill=\"var(--fg)\"/>\n<line x1=\"40\" y1=\"448\" x2=\"56\" y2=\"448\" stroke=\"var(--fg)\" stroke-width=\"2\"/>\n<text x=\"64\" y=\"454\" fill=\"var(--muted)\" font-family=\"var(--sans)\" font-size=\"20\">402 challenge</text>\n<text x=\"210\" y=\"454\" fill=\"var(--fg)\" font-family=\"var(--mono)\" font-size=\"20\">WWW-Authenticate: Payment</text>\n<line x1=\"24\" y1=\"484\" x2=\"40\" y2=\"484\" stroke=\"var(--fg)\" stroke-width=\"2\"/>\n<polygon points=\"56,484 40,476 40,492\" fill=\"var(--fg)\"/>\n<text x=\"64\" y=\"490\" fill=\"var(--muted)\" font-family=\"var(--sans)\" font-size=\"20\">credential</text>\n<text x=\"210\" y=\"490\" fill=\"var(--fg)\" font-family=\"var(--mono)\" font-size=\"20\">Authorization: Payment</text>\n<polygon points=\"24,520 40,512 40,528\" fill=\"var(--fg)\"/>\n<line x1=\"40\" y1=\"520\" x2=\"56\" y2=\"520\" stroke=\"var(--fg)\" stroke-width=\"2\"/>\n<text x=\"64\" y=\"526\" fill=\"var(--muted)\" font-family=\"var(--sans)\" font-size=\"20\">success</text>\n<text x=\"210\" y=\"526\" fill=\"var(--fg)\" font-family=\"var(--mono)\" font-size=\"20\">Payment-Receipt</text>\n<rect x=\"8\" y=\"580\" width=\"544\" height=\"64\" fill=\"var(--tint)\" stroke=\"var(--accent)\" stroke-width=\"1.5\"/>\n<text x=\"280\" y=\"606\" text-anchor=\"middle\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"20\">deliver only after settlement is confirmed;</text>\n<text x=\"280\" y=\"630\" text-anchor=\"middle\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"20\">ambiguous = not delivered</text>\n</svg><figcaption>One handler takes either credential, settles the USDC transferWithAuthorization, and delivers only after settlement is confirmed.</figcaption></figure>\n<p>The server tells them apart by the <code>Authorization</code> header. If the first token is <code>Payment</code>, the request is MPP. Otherwise the server looks at the x402 headers.</p>\n<p>Each route is served under <code>/x402/&lt;name&gt;</code> and <code>/mpp/&lt;name&gt;</code>. Both paths accept either credential.</p>\n<p>The idempotency key we use is:</p>\n<pre><code>protocol:eip3009:network:asset:payer:nonce</code></pre>\n<p>In our implementation MPP is Base only. x402 also runs on Polygon and Solana. Settlement for Base x402 goes through a facilitator (Coinbase CDP). MPP, and x402 on the other networks, we settle ourselves by sending the <code>transferWithAuthorization</code> transaction.</p>\n<p>This is the comparison from our spec.</p>\n<div class=\"table-scroll\"><table><caption>Credential, nonce, and binding differences between x402 and MPP</caption><thead><tr><th scope=\"col\">Field</th><th scope=\"col\">x402</th><th scope=\"col\">MPP</th></tr></thead><tbody><tr><td>Challenge header</td><td><code>PAYMENT-REQUIRED</code> (base64 JSON)</td><td><code>WWW-Authenticate: Payment</code></td></tr><tr><td>Credential header</td><td><code>PAYMENT-SIGNATURE</code></td><td><code>Authorization: Payment</code> plus base64url</td></tr><tr><td>Success header</td><td><code>PAYMENT-RESPONSE</code></td><td><code>Payment-Receipt</code></td></tr><tr><td>EIP-3009 nonce</td><td>Client-chosen random 32 bytes</td><td>Must be keccak256 of the challenge id and the realm, binding the payment to one challenge</td></tr><tr><td>Challenge integrity</td><td>None. The server recomputes the price every time</td><td>HMAC over the challenge fields</td></tr><tr><td>Body binding</td><td>None</td><td><code>Content-Digest</code> of the request body, bound into the HMAC, so a credential cannot be replayed against a different query</td></tr></tbody></table></div>\n<p>x402 leaves the nonce as 32 random bytes chosen by the client. It does not bind the challenge. It does not bind the body. MPP requires the nonce to be keccak256 of the challenge id and the realm, so the payment is bound to one challenge. Its HMAC covers the challenge fields. The same HMAC covers a <code>Content-Digest</code> of the request body, so a credential cannot be replayed against a different query.</p>\n<h2 id=\"things-that-bit-us\">Things that bit us</h2>\n<p>A facilitator timeout during settle leaves the outcome ambiguous. The authorization nonce was never used on chain, nothing was charged, and a retry works. The server must not deliver until settlement is confirmed. Ambiguous means not delivered.</p>\n<p>An overflowing <code>validAfter</code> value passed a naive check but could never settle. That is a free-scrape attack. Numbers must be strict canonical decimals.</p>\n<p>The price must be recomputed server side on every request.</p>\n<p>An empty or failed result must not be charged.</p>\n<h2 id=\"what-the-traffic-says\">What the traffic says</h2>\n<p>In our traffic, about 96 of every 100 payment challenges our servers issued were on <code>/x402/</code> paths and about 4 were on <code>/mpp/</code> paths, about 22 to 1. Settled payments split the same way. In absolute terms, MPP payments were a handful.</p>\n<figure class=\"diagram\"><svg xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" aria-labelledby=\"traffic-title traffic-desc\" viewBox=\"0 0 560 234\" width=\"560\" height=\"234\">\n<title id=\"traffic-title\">x402 vs MPP in our traffic</title>\n<desc id=\"traffic-desc\">Two horizontal bars of relative shares. Payment challenges by path are about 96 percent x402 and 4 percent MPP. Settled payments are about 96 percent x402 and 4 percent MPP.</desc>\n<text x=\"16\" y=\"24\" fill=\"var(--muted)\" font-family=\"var(--sans)\" font-size=\"20\">payment challenges by path</text>\n<text x=\"16\" y=\"50\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"20\">x402 96%</text>\n<text x=\"544\" y=\"50\" text-anchor=\"end\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"20\">MPP 4%</text>\n<rect x=\"16\" y=\"58\" width=\"528\" height=\"28\" fill=\"var(--tint)\" stroke=\"var(--line)\"/>\n<rect x=\"16\" y=\"58\" width=\"506.9\" height=\"28\" fill=\"var(--accent)\"/>\n<rect x=\"522.9\" y=\"58\" width=\"21.1\" height=\"28\" fill=\"var(--fg)\"/>\n<text x=\"16\" y=\"118\" fill=\"var(--muted)\" font-family=\"var(--sans)\" font-size=\"20\">settled payments</text>\n<text x=\"16\" y=\"144\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"20\">x402 96%</text>\n<text x=\"544\" y=\"144\" text-anchor=\"end\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"20\">MPP 4%</text>\n<rect x=\"16\" y=\"152\" width=\"528\" height=\"28\" fill=\"var(--tint)\" stroke=\"var(--line)\"/>\n<rect x=\"16\" y=\"152\" width=\"506.9\" height=\"28\" fill=\"var(--accent)\"/>\n<rect x=\"522.9\" y=\"152\" width=\"21.1\" height=\"28\" fill=\"var(--fg)\"/>\n<rect x=\"16\" y=\"198\" width=\"18\" height=\"18\" fill=\"var(--accent)\"/>\n<text x=\"42\" y=\"213\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"20\">x402</text>\n<rect x=\"120\" y=\"198\" width=\"18\" height=\"18\" fill=\"var(--fg)\"/>\n<text x=\"146\" y=\"213\" fill=\"var(--fg)\" font-family=\"var(--sans)\" font-size=\"20\">MPP</text>\n</svg><figcaption>relative shares, small sample, includes our own tests; path share partly measures discoverability.</figcaption></figure>\n<p>Per challenge issued, the share that ended in a paid call was about the same for both, roughly 0.3 percent. MPP's gap is mostly in how many callers arrive, not in whether they pay once they do.</p>\n<p>About 94 of 100 payment attempts were on Base. Polygon and Solana together were a few percent.</p>\n<h2 id=\"why-mpp-lags\">Why MPP lags</h2>\n<p>We do not know why fewer callers show up for MPP. These are guesses.</p>\n<ul><li>Discovery catalogs and scanners mostly index x402.</li><li>Client libraries and wallets ship x402 first.</li><li>The first-party tooling most agents use already speaks x402.</li><li>MPP's headline feature is sessions. Sessions matter for streaming or per-token billing, which a pay-per-call service does not need. WorkOS describes them as pre-authorized spend, streamed payments, and batch settlement.</li><li>The Stripe and Tempo coupling. WorkOS says MPP carries that dependency.</li></ul>\n<h2 id=\"what-we-would-do-as-a-seller\">What we would do as a seller</h2>\n<p>For a pay-per-call service we would ship x402 first. We would keep the MPP one-shot path in the same handler. The credential is the same <code>transferWithAuthorization</code> on USDC. Both paths already accept either credential. One charge per call does not need a session.</p>\n<p>We would list the <code>/x402/</code> routes where the x402 discovery catalog and the scanners look. We would list the <code>/mpp/</code> routes wherever MPP is indexed.</p>\n<p>Base x402 would keep going through the facilitator (Coinbase CDP). MPP, and x402 on Polygon and Solana, we would keep settling ourselves by sending the transaction.</p>\n<p>If we later billed per token, or charged during a stream, we would look at MPP sessions then. That is new work.</p>\n<h2 id=\"caveats\">Caveats</h2>\n<p>The sample is small and includes our own test purchases and automated checkers. Path is not protocol: both <code>/x402/&lt;name&gt;</code> and <code>/mpp/&lt;name&gt;</code> accept either credential, so a path hit is not the protocol the client chose. The x402 paths are listed in the discovery catalog and by scanners, the <code>/mpp/</code> paths are listed in fewer places, and we have not measured that bias, so we call it <em>likely</em>. Path share partly measures discoverability. We built MPP's one-shot charge intent, the EVM charge flow, not its sessions. The TRM Labs, Artemis, WorkOS, BlackRock, and Cloudflare claims below are theirs.</p>\n<h2 id=\"sources\">Sources</h2>\n<ul><li><a href=\"https://x402.org\" rel=\"noopener\">x402 spec</a></li><li><a href=\"https://mpp.dev\" rel=\"noopener\">MPP spec</a></li><li><a href=\"https://workos.com/blog/x402-vs-stripe-mpp-how-to-choose-payment-infrastructure-for-ai-agents-and-mcp-tools-in-2026\" rel=\"noopener\">WorkOS, on stateless x402, MPP sessions, the IETF submission, the Stripe and Tempo dependency, and one-shot payments</a></li><li><a href=\"https://www.spotedcrypto.com/trm-labs-x402-ai-agent-payments-report-2026/\" rel=\"noopener\">TRM Labs analysis, as reported, on screened x402 volume that looked plausibly agentic</a></li><li><a href=\"https://www.mexc.com/news/901995\" rel=\"noopener\">Artemis estimate, as reported, that about half of x402 transactions were artificial</a></li><li><a href=\"https://www.fintechweekly.com/magazine/articles/blackrock-machine-native-economy-ai-agents-stablecoins-2026\" rel=\"noopener\">Coverage of BlackRock's machine-native economy paper, which discussed x402 and stablecoins</a></li><li><a href=\"https://fortune.com/2026/09/30/cloudflare-tool-businesses-ai-agents-stablecoins\" rel=\"noopener\">Cloudflare's announcement of tooling to charge agents in USDC</a></li></ul>\n</div>\n</article>\n",
      "date_published": "2026-10-06T00:00:00Z",
      "date_modified": "2026-10-06T00:00:00Z",
      "tags": [
        "x402",
        "MPP",
        "HTTP 402",
        "agentic payments",
        "stablecoins",
        "USDC"
      ],
      "language": "en",
      "image": "https://blog.shveik.dev/static/og-default.png",
      "authors": [
        {
          "name": "shveik.dev",
          "url": "https://shveik.dev"
        }
      ]
    }
  ]
}
