MPP: The Machine Payment Protocol and How We Built Dual Payment Support

A deep dive into MPP — the HTTP Authentication Scheme for machine payments on Tempo — and how RobotDomainSearch became one of the first APIs to support both x402 and MPP for true protocol-agnostic agent commerce.

📖 ~14 min read

Agentic commerce is moving fast. New payment protocols are shipping, new chains are going live, and AI agents are becoming the primary consumers of paid APIs. If you’re building infrastructure for this economy, you can’t afford to sit still — you have to meet agents wherever they are, with whatever payment rail they carry.

That’s why we built dual-protocol payment support into RobotDomainSearch. Last week, we wrote about x402 — the protocol that lets AI agents pay for API calls autonomously using USDC on Base. Today, we’re adding a second rail: MPP — the Machine Payment Protocol, an entirely different approach to machine-to-machine payments built on HTTP authentication standards and settling on the Tempo blockchain.

As of today, RobotDomainSearch supports both — x402 and MPP — from the same API endpoints, with the same pricing, on two independent payment rails. We ran 30 end-to-end tests across both protocols: valuation quick, valuation full, and name-search-presence — five of each, for each protocol. 30/30 passed. Same data, same speed, two completely different payment stacks.

If you’re not familiar with x402, start with our deep dive on the x402 protocol. This post covers MPP, why it matters, and how we engineered a system that speaks both languages simultaneously.

What is MPP?

MPP stands for Machine Payment Protocol, formally specified as the “Payment” HTTP Authentication Scheme in draft-ryan-httpauth-payment. Where x402 introduces custom headers like PAYMENT-REQUIRED and PAYMENT-SIGNATURE, MPP takes a different philosophical approach: it uses the existing HTTP authentication framework that’s been part of the web since the beginning.

HTTP already has a well-defined system for authentication challenges. When a server needs credentials, it responds with WWW-Authenticate. When a client provides credentials, it sends Authorization. This is how Basic auth works. How Bearer tokens work. How Digest auth works. MPP adds a new scheme to this family: Payment.

The elegance is in the familiarity. Every HTTP library, every proxy, every load balancer already knows how to handle WWW-Authenticate and Authorization headers. MPP doesn’t fight the web — it extends it.

The MPP Payment Flow

Like x402, the MPP flow is a challenge-response protocol anchored on HTTP 402. Here’s how it works in five steps:

1. Agent makes a request

An AI agent sends a standard HTTP request to a paid endpoint — no special headers needed.

GET /v1/valuation/example.com HTTP/1.1
Host: api.robotdomainsearch.com

2. Server responds with 402 + authentication challenge

The server responds with 402 Payment Required and a WWW-Authenticate header containing the payment challenge. This follows the exact format defined in RFC 9110 §11.6.1 for authentication challenges:

HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="abc123...", realm="robotdomainsearch.com",
  method="tempo", intent="charge", request="eyJhbW91bnQiOiIyMDAwMCI..."
Cache-Control: no-store

The challenge parameters tell the agent everything it needs to know:

  • id — An HMAC-bound challenge identifier (more on this below)
  • realm — The protection space (the API domain)
  • method — The payment method (tempo)
  • intent — What kind of payment (charge = one-time)
  • request — Base64url-encoded payment details (amount, currency, recipient, chain)

Decoding the request parameter reveals the payment details:

{
  "amount": "20000",
  "currency": "0x20c0000000000000...b9537d11c60e8b50",
  "recipient": "0xYourWalletAddress",
  "methodDetails": {
    "chainId": 4217
  }
}

The amount is in the token’s smallest unit — with 6 decimals, 20000 = $0.02. The currency is the USDC.e (bridged USDC) contract address on Tempo. Chain ID 4217 identifies the Tempo mainnet.

3. Agent creates a payment on Tempo

The agent decodes the challenge, sees the amount and recipient, and creates a TIP-20 token transfer on the Tempo blockchain. The agent can either broadcast the transaction itself (push mode) or sign it and let the server broadcast it (pull mode).

4. Agent retries with payment credential

The agent re-sends the original request with an Authorization: Payment header containing a base64url-encoded credential:

GET /v1/valuation/example.com HTTP/1.1
Host: api.robotdomainsearch.com
Authorization: Payment eyJjaGFsbGVuZ2UiOnsiaWQiOiJhYmMxMjMuLi4i...

The decoded credential contains the echoed challenge plus payment proof:

{
  "challenge": {
    "id": "abc123...",
    "realm": "robotdomainsearch.com",
    "method": "tempo",
    "intent": "charge",
    "request": "eyJhbW91bnQiOiIyMDAwMCI..."
  },
  "payload": {
    "type": "hash",
    "hash": "0x7a3f..."
  }
}

5. Server verifies payment and responds

The server verifies the challenge ID via HMAC, checks that the challenge hasn’t expired, verifies the on-chain payment, and returns the requested data with a Payment-Receipt header:

HTTP/1.1 200 OK
Payment-Receipt: eyJtZXRob2QiOiJ0ZW1wbyIsInJlZmVyZW5jZSI6IjB4...
Content-Type: application/json

{
  "domain": "example.com",
  "estimate": { "low": 5000000, "mid": 12000000, "high": 25000000 },
  ...
}

The receipt confirms the payment method, the transaction reference, and the verification status — a cryptographic proof that the exchange happened.

How MPP Differs from x402

Both protocols solve the same problem — machine-to-machine payments over HTTP — but they make fundamentally different design choices:

Feature x402 MPP
HTTP mechanism Custom headers (PAYMENT-REQUIRED / PAYMENT-SIGNATURE) Standard HTTP Auth (WWW-Authenticate / Authorization)
Challenge binding Implicit (payment requirements per route) HMAC-bound challenge IDs (stateless, tamper-proof)
Settlement chain Base (EVM L2, chain ID 8453) Tempo (EVM-compatible, chain ID 4217)
Token USDC USDC.e (bridged USDC, TIP-20)
Error format JSON response body RFC 9457 Problem Details
Payment modes Facilitator-verified Push (tx hash) and Pull (signed tx for server broadcast)
Spec status Open standard by Coinbase IETF draft (draft-ryan-httpauth-payment)

Neither protocol is “better” — they’re optimized for different ecosystems and design philosophies. x402 is production-hardened with a massive ecosystem on Base. MPP brings HTTP standards compliance and the Tempo chain’s settlement model. Supporting both means agents have options.

Why Tempo?

Tempo is an EVM-compatible blockchain with chain ID 4217. If you’ve worked with Ethereum, Base, or any other EVM chain, Tempo feels instantly familiar — same JSON-RPC API, same address format, same tooling.

USDC.e is a bridged USDC stablecoin on Tempo — a TIP-20 token (equivalent to ERC-20) pegged to USD. Since it’s the same USDC that agents already use on Base, just bridged via Stargate, both parties get the same price stability without taking on currency risk.

Why does this matter for agents? Three reasons:

  1. More chains = more optionality. An agent with a Tempo wallet can access services that an agent locked to Base alone cannot. As the agent economy grows, protocol diversity becomes a competitive advantage.

  2. Familiar developer experience. Because Tempo is EVM-compatible, the same go-ethereum client library, the same Transfer event signatures, and the same address validation work out of the box. Our implementation uses ethclient.Dial() to connect to a Tempo node — identical to connecting to Base or Ethereum mainnet.

  3. Ecosystem resilience. Single-chain dependence is a risk. If Base has congestion, high gas, or downtime, agents with MPP support can fall back to Tempo. Protocol diversity is infrastructure diversity.

How We Built Dual Protocol Support

This is where it gets interesting. Supporting two payment protocols from the same API endpoints isn’t just “add another middleware.” It requires careful architecture to handle protocol detection, challenge generation, payment verification, and error handling — all without making either protocol a second-class citizen.

The Payment Router

At the heart of our dual-protocol system is a payment router — a middleware that wraps the existing x402 middleware and adds MPP support on top. The router makes a decision based on the request headers:

┌─────────────────────────────────────┐
│          Incoming Request           │
└──────────────┬──────────────────────┘
               │
       ┌───────▼────────┐
       │ Authorization:  │──── Yes ──→ MPP Payment Flow
       │   Payment?      │
       └───────┬─────────┘
               │ No
       ┌───────▼────────┐
       │  X-PAYMENT      │──── Yes ──→ x402 Payment Flow
       │  header?        │
       └───────┬─────────┘
               │ No
       ┌───────▼────────┐
       │  Paid route?    │──── No ───→ Pass Through (free endpoint)
       └───────┬─────────┘
               │ Yes
       ┌───────▼────────┐
       │ Dual Challenge  │──── 402 with BOTH protocols
       └─────────────────┘

Three headers, three paths, zero ambiguity. Here’s the key insight from our implementation: when MPP is disabled (the MPP_ENABLED environment variable isn’t set to true), the router becomes a transparent pass-through to x402 — zero overhead, zero risk:

func NewPaymentRouter(cfg *PricingConfig, mppCfg *MPPConfig,
    x402MW func(http.Handler) http.Handler, db *sql.DB,
) func(http.Handler) http.Handler {
    if !mppCfg.Enabled {
        return x402MW // Transparent pass-through
    }
    // ... dual-protocol routing logic
}

This means existing deployments that only use x402 aren’t affected at all. MPP is purely additive.

Stateless Challenge Verification (HMAC)

One of MPP’s most elegant design decisions is stateless challenge verification. When the server issues a challenge, it doesn’t need to store it in a database or cache. Instead, the challenge ID itself is a cryptographic proof of its contents.

Here’s how it works: the challenge ID is computed as an HMAC-SHA256 of all challenge parameters, keyed with a server secret:

func ComputeChallengeID(c *MPPChallenge, secretKey string) string {
    // Canonical input: each field in a fixed positional slot
    input := strings.Join([]string{
        c.Realm,                            // "robotdomainsearch.com"
        c.Method,                           // "tempo"
        c.Intent,                           // "charge"
        serializeRequestForHMAC(c.Request), // base64url(canonicalJSON)
        c.Expires,                          // RFC 3339 timestamp
        c.Digest,
        opaqueSerialized,
    }, "|")

    mac := hmac.New(sha256.New, []byte(secretKey))
    mac.Write([]byte(input))
    return base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
}

When a client returns a credential with an echoed challenge, the server recomputes the HMAC from the echoed parameters and compares it to the challenge ID using constant-time comparison (to prevent timing attacks). If any parameter has been tampered with — amount, recipient, expiration, anything — the HMAC won’t match and the request is rejected.

No database lookup. No Redis cache. No distributed state. The math is the verification.

On-Chain Verification

Once the challenge is verified, the server needs to confirm that actual money moved on-chain. Our implementation supports two modes:

Push mode — The client already broadcast the transaction and sends the hash. The server fetches the receipt from the Tempo node, decodes the TIP-20 Transfer event logs, and verifies the amount, recipient, and token contract:

func (t *TempoMethod) verifyPushMode(ctx context.Context,
    cred *MPPCredential) (*MPPReceipt, error) {
    hash := cred.Payload["hash"].(string)

    // 1. Replay protection — has this tx hash been used before?
    if err := t.checkReplay(ctx, hash); err != nil {
        return nil, err
    }

    // 2. Fetch transaction receipt from Tempo
    receipt, err := t.rpc.TransactionReceipt(ctx, common.HexToHash(hash))

    // 3. Decode Transfer event and verify amount/recipient/token
    _, err = VerifyTransfer(receipt, t.config.Recipient,
        t.config.Currency, expectedAmount)

    // 4. Check minimum block confirmations (default: 6)
    currentBlock, _ := t.rpc.BlockNumber(ctx)
    confirmations := currentBlock - receipt.BlockNumber.Uint64()
    if confirmations < t.config.MinConfirmations {
        return nil, fmt.Errorf("insufficient confirmations: %d (need %d)",
            confirmations, t.config.MinConfirmations)
    }

    return &MPPReceipt{
        Method: "tempo", Reference: hash, Status: "success",
    }, nil
}

Pull mode — The client sends signed transaction bytes and the server broadcasts it. This is useful for agents that don’t have direct access to a Tempo RPC endpoint. The server calls eth_sendRawTransaction, waits for the receipt, then verifies the Transfer event the same way.

Both modes include replay protection via the payment_logs database table. Every transaction hash is recorded after successful verification — attempt to reuse the same hash and you get a verification-failed error.

The confirmation check also includes a uint64 underflow guard. If a chain reorganization causes the current block to temporarily be less than the transaction block, the subtraction would underflow in unsigned arithmetic. Our implementation checks for this explicitly:

var confirmations uint64
if currentBlock > txBlock {
    confirmations = currentBlock - txBlock
}
// else: reorg or RPC inconsistency → 0 confirmations (safe default)

Small detail. The kind of thing that prevents a production incident.

The Dual Challenge Response

The most interesting behavior happens when a request arrives with no payment credentials at all. The router captures the x402 middleware’s 402 response — which already contains the X-PAYMENT-REQUIREMENTS header — and adds MPP WWW-Authenticate headers to the same response:

func issueDualChallenge(w http.ResponseWriter, r *http.Request,
    x402Handler http.Handler, cfg *PricingConfig, mppCfg *MPPConfig) {

    // Capture x402's 402 response
    capturer := &responseCapturer{...}
    x402Handler.ServeHTTP(capturer, r)

    // Add MPP challenges for each payment method
    for _, method := range mppCfg.Methods {
        challenge := NewMPPChallenge(method, mppCfg.Realm,
            priceUSD, description, mppCfg.SecretKey, mppCfg.TimeoutSeconds)
        w.Header().Add("WWW-Authenticate", SerializeChallenge(challenge))
    }

    // Copy x402's headers (X-PAYMENT-REQUIREMENTS, etc.)
    for k, vals := range capturer.Header() {
        for _, v := range vals {
            w.Header().Add(k, v)
        }
    }

    w.WriteHeader(http.StatusPaymentRequired)
}

The result is a single 402 response that contains everything a client needs for either protocol:

HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="abc123...", realm="robotdomainsearch.com",
  method="tempo", intent="charge", request="eyJ..."
X-PAYMENT-REQUIREMENTS: eyJwcmljZSI6IjAuMDIiLCJjdXJyZW5jeSI6IlVTREMi...
Cache-Control: no-store

An agent with an x402 wallet reads X-PAYMENT-REQUIREMENTS and pays with USDC on Base. An agent with an MPP wallet reads WWW-Authenticate: Payment and pays with USDC.e on Tempo. Same endpoint. Same price. Two completely independent payment rails.

Pluggable Payment Methods

The MPP implementation is built around a clean interface that makes adding new payment methods straightforward:

type MPPPaymentMethod interface {
    Name() string                                  // "tempo"
    Intent() string                                // "charge"
    BuildRequest(priceUSD string) map[string]any   // Challenge request object
    Verify(ctx context.Context, cred *MPPCredential) (*MPPReceipt, error)
}

Tempo is the first method. But the interface is designed for extensibility — adding a second chain (say, Solana-based MPP, or a Stripe fiat method) means implementing four functions. The router, challenge generation, and dual-challenge logic all work automatically with any method that satisfies this interface.

Price conversion uses math/big rational arithmetic to avoid float64 rounding errors — because when you’re converting $0.02 to 20000 token units across 6 decimals, a floating-point rounding error means you’re charging the wrong amount:

func (t *TempoMethod) usdToTokenAmount(priceUSD string) string {
    price := strings.TrimPrefix(priceUSD, "$")

    // Parse as rational number for exact decimal representation
    rat := new(big.Rat)
    rat.SetString(price)

    // Multiply by 10^decimals (e.g., 10^6 for USDC-like tokens)
    multiplier := new(big.Int).Exp(big.NewInt(10),
        big.NewInt(int64(t.config.Decimals)), nil)
    rat.Mul(rat, new(big.Rat).SetInt(multiplier))

    // Truncate to integer — exact result, no float rounding
    result := new(big.Int).Div(rat.Num(), rat.Denom())
    return result.String()
}

Error Handling: RFC 9457 Problem Details

When something goes wrong in the MPP flow, the server responds with structured errors following RFC 9457 Problem Details. This is a standard format for machine-readable HTTP error responses:

{
  "type": "https://paymentauth.org/problems/payment-expired",
  "title": "Payment Expired",
  "status": 402,
  "detail": "Challenge has expired."
}

Each error type maps to a specific problem:

Error Type Meaning
payment-required No credential provided
payment-insufficient Amount below the required minimum
payment-expired Challenge has passed its expiration time
verification-failed On-chain verification failed
method-unsupported Unknown payment method
malformed-credential Credential couldn’t be decoded
invalid-challenge HMAC verification failed (tampering detected)

Every error response also includes a fresh challenge in the WWW-Authenticate header, so the client can immediately retry without making a separate request to get new payment details. This is a nice UX touch — one round-trip instead of two on recoverable errors.

Testing Dual Protocol Support

We don’t ship payment infrastructure without thorough testing. Our test suite covers the full stack:

  • Unit tests — HMAC computation, challenge serialization and round-trips, credential parsing, base64url encoding edge cases
  • Mock RPC tests — Tempo on-chain verification with a mock TempoRPC interface, covering successful transfers, wrong recipients, wrong tokens, insufficient amounts, and transaction failures
  • Router integration tests — Dual challenge generation (verifying both WWW-Authenticate and X-PAYMENT-REQUIREMENTS appear), MPP credential routing, x402 passthrough, API key bypass, free endpoint pass-through
  • End-to-end tests — Push mode and pull mode payment flows through the full middleware stack
  • Edge cases — Expired challenges, tampered challenge IDs, replay detection (same tx hash twice), insufficient block confirmations, uint64 underflow guard on block number subtraction

The 30/30 acceptance test covered real endpoint calls: valuation quick ×5, valuation full ×5, and name-search-presence ×5 — for each protocol independently. Same data returned, same response times, two different payment settlement paths.

Using RobotDomainSearch with MPP

If you’re building an agent that speaks MPP, here’s what you need to know.

Endpoint Pricing

The pricing is identical whether you pay via x402 or MPP:

Endpoint Cost What It Does
/v1/check Free Domain availability across 500+ TLDs
/v1/valuation/:domain $0.02 Valuation with comparable sales data
/v1/name-search-presence $0.02 Online presence scoring
/v1/intel $0.05 Deep intel — parking detection, screenshots, redirects

The MPP Flow in Practice

Here’s the three-step exchange for a domain valuation:

Step 1: Get the challenge

curl -i https://api.robotdomainsearch.com/v1/valuation/example.com

Response includes both protocol options:

HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="...", realm="robotdomainsearch.com",
  method="tempo", intent="charge", request="eyJhbW91bnQiOiIyMDAwMCI..."
X-PAYMENT-REQUIREMENTS: eyJwcmljZSI6IjAuMDIi...

Step 2: Make the payment on Tempo and build the credential

Create a USDC.e transfer on Tempo for the amount specified in the challenge’s request field. Encode the credential as base64url JSON containing the echoed challenge and the transaction hash.

Step 3: Retry with the credential

curl -i https://api.robotdomainsearch.com/v1/valuation/example.com \
  -H "Authorization: Payment eyJjaGFsbGVuZ2UiOnsiaWQiOi..."

Response includes the data and a payment receipt:

HTTP/1.1 200 OK
Payment-Receipt: eyJtZXRob2QiOiJ0ZW1wbyIsInN0YXR1cyI6InN1Y2Nlc3Mi...
Content-Type: application/json

{
  "domain": "example.com",
  "estimate": { "low": 5000000, "mid": 12000000, "high": 25000000 },
  ...
}

Agents with x402 wallets (USDC on Base) continue to work exactly as before. MPP is purely additive — it adds a new payment rail without changing anything about the existing one.

What This Means for the Agent Economy

Supporting multiple payment protocols isn’t just a technical exercise. It’s about building infrastructure that’s resilient, inclusive, and ready for a future where agents are the primary consumers of web services.

Protocol Diversity = Resilience

The internet doesn’t rely on a single routing protocol. DNS doesn’t depend on a single root server. Payment infrastructure for agents shouldn’t depend on a single chain or a single protocol either. When an API supports both x402 and MPP, agents have a fallback. If Base is congested, pay via Tempo. If one protocol’s tooling doesn’t support your language, use the other.

The Dual-Challenge Pattern

The pattern we’ve built — issuing challenges for multiple payment protocols in a single 402 response — could become a standard. Any API that wants to maximize its addressable market can offer multiple payment rails simultaneously. The client picks the one it supports. No negotiation, no feature flags, no version parameters. Just standard HTTP semantics.

Pluggable Future

The MPPPaymentMethod interface means our third payment protocol is a matter of implementing four functions. Solana-based MPP? A Stripe fiat bridge for agents with traditional funding? The architecture is ready. Each new method gets automatic dual-challenge support, the same HMAC verification, and the same RFC 9457 error handling.

The Wallet of Tomorrow

Today, most agent wallets speak one protocol. Tomorrow, multi-protocol wallets will be the norm. An agent will carry both USDC on Base and USDC.e on Tempo — the same way a person carries both Visa and Mastercard. The services that support multiple protocols today will be the easiest to integrate with when that shift happens.

Getting Started

The agent economy is multi-protocol. Here’s how to explore:


One payment protocol proved that machines can pay for services autonomously. Two payment protocols prove that the agent economy doesn’t have to be a monoculture.

The future of machine commerce isn’t about picking the winning protocol. It’s about building infrastructure flexible enough to speak all of them. That’s what dual-protocol support means — and it’s just the beginning.