Skip to content
Go back

MPP vs x402: Two Competing Answers to the Same HTTP 402

Two protocols answer HTTP’s thirty-year-old, never-implemented 402 Payment Required status code, and Amazon Bedrock AgentCore Payments speaks both. x402 (Coinbase) and MPP (Stripe and Tempo) are direct competitors for the same job: an agent hits a paywalled resource and pays for it inline, no API key, no billing account set up in advance. They disagree about what “pay” is allowed to mean.

Both protocols follow that identical outer loop. Where they diverge is what each one chooses to standardize.

Same shape, different commitments

x402MPP
SettlementOn-chain only — stablecoins, primarily USDC on BaseMulti-rail — Tempo stablecoins, Stripe cards/ACH/BNPL via Shared Payment Tokens, Bitcoin via Lightning
Retry headerPAYMENT-SIGNATURE (v2) or X-PAYMENT (v1)Authorization: Payment <base64url-token>
Pricing modelexact (fixed price) and upto (metered, settle-for-less-than-authorized)Charge intent, plus a session primitive for high-frequency metered billing
FacilitatorRequired — a third party verifies and settles the on-chain transferFramed as a formal HTTP auth scheme; some methods avoid a separate facilitator dependency
TransportHTTP onlyHTTP, plus an MCP transport binding — MCP tool servers can charge per tool call directly
BackingCoinbase, fully open and permissionlessStripe + Tempo, with Visa and Lightspark extending it to cards and Lightning

AgentCore’s release notes describe x402’s exact and upto schemes and MPP as three parallel options behind one ProcessPayment API — you pick paymentType (CRYPTO_X402 or MPP) per call, and the wallet, budget check, and signing path are identical underneath.

x402’s upto scheme: pay for what you actually used

upto lets a merchant advertise a ceiling instead of a fixed price. The buyer’s wallet authorizes a Permit2 allowance up to that ceiling, and the merchant settles for whatever was actually consumed — no larger than the ceiling, no second round trip. That’s what makes per-token LLM inference or metered compute billable in a single request/response instead of a price negotiation.

I proved this by extending a local x402 merchant with UptoEvmScheme and a Settlement-Overrides response header carrying the real charge:

// merchant route: authorize up to a ceiling, settle for less
app.get("/metered-recap", (_req, res) => {
  res.setHeader(
    "Settlement-Overrides",
    JSON.stringify({ amount: "1000" }),  // actual metered charge
  );
  res.json({ status: "paid", settledUnits: "1000" });
});
# buyer: authorize a ceiling, let the merchant settle lower
payment = client.process_payment(
    ...,
    paymentType="CRYPTO_X402",
    paymentInput={"cryptoX402": {
        "version": "2",
        "payload": accept,
        "permit2AllowanceLimit": accept["amount"],  # the ceiling
    }},
)

ProcessPayment authorized a ceiling of 15000 base units on Base Sepolia testnet; the merchant declared an actual charge of 2000. Checking the wallet’s on-chain balance immediately before and after confirmed it moved by exactly 2000, not 15000 — real settlement tracked real usage, not the authorization.

The gotcha: the ProcessPayment X-Ray span’s payments.spend_amount attribute reads the ceiling, not the settled amount, because AgentCore signs the authorization before the merchant declares what it actually consumed. A spend dashboard built on that span attribute alone will overstate real spend for upto transactions — reconcile against actual settlement, not the span, to get the true number.

MPP: one interface, four rails behind it

MPP standardizes the challenge-response interface and lets the merchant advertise whichever payment methods it accepts — evm, tempo, solana, and through Stripe, card and fiat — inside one WWW-Authenticate: Payment header, per the mpp-specs IETF draft co-authored by Tempo and Stripe.

No live MPP-compatible merchant exists anywhere yet to round-trip against — not from AWS, not in Coinbase’s Bazaar. To prove AgentCore’s side of the handshake, I built the smallest thing that could: a synthetic, spec-compliant evm-method challenge.

WWW-Authenticate: Payment id="<unique-id>", realm="<realm>",
  method="evm", intent="charge",
  request="<base64url JSON: amount, currency, recipient, methodDetails.chainId>"
payment = client.process_payment(
    ...,
    paymentType="MPP",
    paymentInput={"mpp": {
        "version": "1",
        "wwwAuthenticateHeaders": [header_value],  # forwarded verbatim
    }},
)
credential = payment["paymentOutput"]["mpp"]["paymentCredential"]
# -> "Payment <base64url-token>", ready to attach as Authorization

Two checks confirmed AgentCore genuinely parsed the challenge rather than rubber-stamping it: the session’s available budget dropped by exactly the amount declared in the request (0.020.019 USD for a $0.001 charge), and the returned credential was a well-formed, 1176-character token with the documented Payment prefix. That’s genuine credential generation, matching the ProcessPayment response schema exactly. What it doesn’t prove is settlement — there’s nothing real to settle against yet.

When to reach for which

AgentCore Payments doesn’t make you choose between them. It abstracts the protocol behind one API, so the decision moves from “which protocol do I integrate” to “which protocol does this merchant accept” — which is the right place for that decision to live.

The open thread

Full settlement against a live MPP merchant hasn’t been tested anywhere yet — no public merchant exists that accepts MPP payments through the complete cycle. I’ve proved that AgentCore can generate valid credentials and that budget tracking works, but settling a real charge against a real merchant and confirming the merchant received payment remains untested. This is a genuine gap, not a missing step in the documentation. Once live MPP merchants exist in production, the full picture — credential generation, settlement success, reconciliation — will be available to verify.


Share this post on:


Previous Post
A Browser That Pays
Next Post
Benchmark saturation is real. The price collapse is an illusion.