MPP

CICode ScanningGitHub

Elixir implementation of the Machine Payments Protocol (MPP) — HTTP 402 payment middleware for AI agents and machine-to-machine commerce.

What is MPP?

MPP is an open standard for machine-to-machine payments via HTTP 402, co-developed by Stripe and Tempo Labs. It enables any API to charge per-request without user accounts, API keys, or signup flows.

Payment is authentication. An agent hits your endpoint, gets a 402 challenge, pays, and receives the response — all in a single HTTP roundtrip.

How It Works

Client Server
│ │
│─── GET /api/data ──────────────────────►│
│ │
│◄── 402 Payment Required ───────────────│
│ WWW-Authenticate: Payment │
│ (challenge with price + method) │
│ │
│ [Client fulfills payment] │
│ │
│─── GET /api/data ──────────────────────►│
│ Authorization: Payment <credential> │
│ │
│◄── 200 OK + Payment-Receipt ───────────│
│ (resource + proof of payment) │
│ │

Quick Start

Mount MPP.Plug in your Phoenix router to gate any endpoint behind payment:

defmodule MyAppWeb.Router do
use MyAppWeb, :router
pipeline :paid do
plug MPP.Plug,
secret_key: "your-hmac-secret",
realm: "api.example.com",
method: MPP.Methods.Stripe,
amount: "5000",
currency: "usd",
method_config: %{
"stripe_secret_key" => "sk_test_...",
"network_id" => "profile_1Mqx...",
"payment_method_types" => ["card"]
}
end
scope "/premium", MyAppWeb do
pipe_through [:api, :paid]
get "/data", DataController, :show
end
end

Tempo (Stablecoins)

pipeline :paid_tempo do
plug MPP.Plug,
secret_key: "your-hmac-secret",
realm: "api.example.com",
method: MPP.Methods.Tempo,
amount: "1000000",
currency: "0x...(pathUSD token address)",
recipient: "0x...your-address",
method_config: %{
"rpc_url" => "https://rpc.tempo.xyz",
"chain_id" => 4217,
"fee_payer" => true,
"fee_payer_private_key" => "0x...",
"fee_token" => "0x...(fee token address)",
"wait_for_confirmation" => false,
"memo" => "0x...(optional 32-byte memo)"
}
end

EVM (Ethereum, Base, Polygon, etc.)

pipeline :paid_evm do
plug MPP.Plug,
secret_key: "your-hmac-secret",
realm: "api.example.com",
method: MPP.Methods.EVM,
amount: "1000000",
currency: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
recipient: "0x...your-address",
method_config: %{
"rpc_url" => "https://mainnet.infura.io/v3/YOUR_KEY",
"chain_id" => 1
}
end

Currency is the ERC-20 token contract address (e.g., USDC above). For native ETH, use "ETH" or the zero address. The client broadcasts a transaction, then sends the hash as a credential.

Multi-Method (Stripe + Tempo)

Offer multiple payment options in a single 402 response — the agent picks whichever it can pay with:

pipeline :paid_multi do
plug MPP.Plug,
secret_key: "your-hmac-secret",
realm: "api.example.com",
methods: [
[
method: MPP.Methods.Stripe,
amount: "5000",
currency: "usd",
method_config: %{"stripe_secret_key" => "sk_test_..."}
],
[
method: MPP.Methods.Tempo,
amount: "5000000",
currency: "0x...(pathUSD)",
recipient: "0x...",
method_config: %{"rpc_url" => "https://rpc.tempo.xyz"}
]
]
end

Requests without payment get a 402 Payment Required with a challenge. Requests with a valid Authorization: Payment credential pass through with a Payment-Receipt header and the receipt in conn.assigns[:mpp_receipt].

Each route can have its own pricing — just mount MPP.Plug with different amount/currency per pipeline or scope.

What This Means for Your API

Today, monetizing an API means building a billing system: user accounts, API key provisioning, usage tracking, rate limiting, a pricing page, a dashboard. That's months of work before you earn a cent.

With MPP, you add one Plug to your router and your API charges per-request. No accounts. No API keys. No billing infrastructure. The payment is the authentication.

Use cases:

For AI agents: Your API becomes callable by any agent with a wallet. No onboarding flow, no API key provisioning, no approval process. The agent discovers the price from the 402 response, pays, and gets the resource. That's it — your API just acquired a customer in one HTTP roundtrip.

Why MPP?

Payment Methods

MethodProtocolSettlementStatus
StripeMPPFiat (cards, wallets)v0.1.0
TempoMPPStablecoins (TIP-20)v0.2.0
EVMMPPAny EVM chain (ETH, USDC, ERC-20)v0.3.0
LightningMPPBitcoin (BOLT11)Future

The server can offer multiple payment methods in a single 402 response. The agent picks whichever it can pay with.

Tempo capabilities: Fee payer co-signing (server sponsors gas), optimistic broadcast (respond before block inclusion), memo matching for transaction tagging, and pluggable dedup stores with a built-in ETS+TTL option via ConCache.

Tempo networks:Mainnet (chain ID 4217, rpc.tempo.xyz) | Testnet (Moderato) (chain ID 42431, rpc.moderato.tempo.xyz)

Modules

ModulePurpose
MPP.PlugPlug middleware — the main integration point
MPP.Plug.ConfigValidated endpoint config (shared settings + method entries)
MPP.Plug.MethodEntryPer-method config within a multi-method endpoint
MPP.ChallengeHMAC-SHA256 bound challenge creation/verification
MPP.CredentialPayment credential encoding/decoding
MPP.ReceiptProof-of-payment receipt serialization
MPP.HeadersWWW-Authenticate (incl. multi-challenge), Authorization, Payment-Receipt headers
MPP.ErrorsRFC 9457 Problem Detail error types (incl. session error types)
MPP.VerifierTransport-neutral verification pipeline (HMAC, realm, expiry, request match, method.verify)
MPP.JCSRFC 8785 JSON Canonicalization (MPP subset) for cross-SDK HMAC interop
MPP.BodyDigestSHA-256 body digest compute/verify for request body binding
MPP.AmountAmount/decimals helpers: parse_units, with_base_units, parse_dollar_amount
MPP.MethodBehaviour for pluggable payment methods
MPP.Intents.ChargeCharge intent request schema
MPP.Methods.StripeStripe SPT payment verification
MPP.Methods.TempoTempo on-chain TIP-20 transfer verification via onchain_tempo
MPP.Methods.EVMGeneric EVM on-chain transfer verification (any chain) via onchain
MPP.Tempo.StoreBehaviour for pluggable transaction dedup stores
MPP.Tempo.ConCacheStoreBuilt-in ETS dedup store with TTL via ConCache
MPP.McpMCP (JSON-RPC) transport: error codes, meta keys, server/client helpers
MPP.Client.PaymentProviderBehaviour for client-side payment providers (supports?/3, pay/2)
MPP.Client.MultiProviderMulti-provider dispatch with first-match routing

Installation

def deps do
[
{:mpp, "~> 0.4.0"}
]
end

onchain, onchain_tempo, and con_cache are pulled in automatically — no extra setup for EVM, Tempo, or the built-in MPP.Tempo.ConCacheStore dedup store.

Live Example

Strip0x — blockchain tools API using MPP with Tempo payments. $0.0001 per paid request (100 base units USDC.e on Tempo mainnet).

# Free endpoint (no payment needed)
curl "https://strip0x.com/api/hex/encode?value=hello"
# See the 402 challenge on a paid endpoint
curl -i "https://strip0x.com/api/address/validate?address=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
# Pay and get the response (~2s round-trip including on-chain settlement)
tempo request -t -X GET "https://strip0x.com/api/address/validate?address=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
# Machine-readable discovery (OpenAPI 3.1 with x-payment-info extensions)
curl https://strip0x.com/openapi.json

Observed latency: ~2s end-to-end for a paid request (402 challenge + Tempo on-chain TIP-20 transfer + credential retry). Free endpoints respond in ~70ms (network only — business logic is sub-10μs on the BEAM).

Try it and open an issue if anything breaks.

Continuous Integration

Two GitHub Actions workflows gate the repo (Elixir/OTP pinned via .tool-versions, so CI never drifts from local mix format):

A third workflow, Code Scanning (.github/workflows/code-scanning.yml), uploads Sobelow findings to the Security → Code scanning tab (CodeQL has no Elixir support). Security vulnerabilities should be reported privately — see SECURITY.md.

References

License

MIT — see LICENSE for details.