Payments with MPP
Pay for sessions and streams with the open Machine Payments Protocol on Tenzro Network: challenges, credentials, signed vouchers and receipts.
The Machine Payments Protocol (MPP) is an open protocol for machine-to-machine payments over HTTP. It uses the same 402 Payment Required handshake as x402, carried in the IETF Payment HTTP authentication scheme, and adds sessions: a payer funds a session once, then pays for each unit of work with a signed voucher. That makes it the natural choice for streamed inference billed per token, long-running jobs and anything metered. Tenzro nodes accept MPP on every paid route.
Prerequisites
- A wallet with TNZO or stablecoins: a passkey wallet from the console or a delegated agent wallet from Build an AI payment agent.
curlandjq.- Background: MPP.
1. See what a payment costs
Prices on Tenzro are often set in USD and paid in TNZO at the consensus rate. Ask the node what one US dollar costs:
curl -s https://rpc.tenzro.com \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tenzro_quotePayment","params":{"usd_e6":1000000}}' | jq .The result is {asset, amount, usd_e6}: the asset, the amount in its smallest unit, and the USD price you asked about. tenzro payment quote --usd-e6 1000000 does the same from the CLI.
2. Receive a challenge
Ask for a paid resource with no credential. The node answers 402 with an MPP challenge in the WWW-Authenticate: Payment header and as a JSON body:
HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment id="ch_c41e09", realm="/v1/chat/completions", method="mpp", intent="charge", request="eyJjaGFsbGVuZ2VfaWQiOi...", expires="2026-10-02T12:35:00Z"
Payment-Required: true{
"challenge_id": "ch_c41e09",
"protocol": "mpp",
"resource": "/v1/chat/completions",
"amount": 250000,
"asset": "USDC",
"recipient": "0x<provider-wallet>",
"chain": "<caip2>",
"expires_at": "2026-10-02T12:35:00Z",
"extra": {}
}If you run the resource server yourself on a Tenzro node, you can issue challenges directly. Pass an app_id to add your registered developer margin on top of the network cost; settlement routes the margin to your app wallet.
curl -s https://rpc.tenzro.com \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tenzro_createPaymentChallenge","params":{"protocol":"mpp","resource":"/v1/weather","amount":"250000","asset":"USDC","recipient":"0x<your-wallet>"}}'3. Answer with a credential
The credential names the challenge, the payer and the amount, and carries a hybrid classical and ML-DSA-65 signature from the payer's account. Your wallet signs inside its hardware. Wrap it in the IETF Payment body (the challenge it answers, the payer and the credential as the payload), send that base64url-encoded in the Authorization header, and retry the request:
CRED=$(jq -c '{challenge: {id: .challenge_id}, source: .payer_did, payload: .}' credential.json \
| base64 -w0 | tr '+/' '-_' | tr -d '=')
curl -s https://rpc.tenzro.com/v1/chat/completions \
-H 'content-type: application/json' \
-H "Authorization: Payment $CRED" \
-d '{"model":"qwen3.6-35b-a3b","stream":true,"messages":[{"role":"user","content":"Explain rental escrow."}]}'The credential JSON has the same fields as for x402 (credential_id, challenge_id, protocol: "mpp", payer_did, payer_address, amount, asset and the signatures). The node checks the payer's identity and delegation scope before it verifies the payment, so an agent can never pay outside the limits its controller set.
4. Pay per unit with session vouchers
For a stream, the session runs over a payment channel. The payer funds an escrow to the provider once, opens a channel over it, and then signs a voucher as work arrives. A session challenge carries intent session and the escrow's id. Each voucher commits to the chain, the escrow, the cumulative amount spent so far and, once work has been delivered, the head of the receipt chain it pays for:
# Fund the session and open the channel over it (the escrow id is the channel id)
tenzro escrow create --payee 0x<provider-wallet> --amount 5000000 --expires-at <unix-ms> --rpc https://rpc.tenzro.com
tenzro escrow open-channel --channel-id <escrow-id> --rpc https://rpc.tenzro.com
# Pay for the work so far: the new cumulative total and what this voucher adds
tenzro escrow voucher <escrow-id> --cumulative 1200 --charge 400 --rpc https://rpc.tenzro.comThe provider accepts a voucher only if:
- the signature verifies as the payer that funded the escrow;
- the channel is open;
- the cumulative amount is higher than the last voucher's, so a voucher cannot be replayed;
- the cumulative amount does not exceed the escrow.
When the stream ends, the payer closes the channel (tenzro escrow close-channel <escrow-id>, JSON-RPC tenzro_closePaymentChannel) and only the highest voucher is claimed on the ledger. Many vouchers become one settlement, and the rest of the escrow goes back to the payer when it expires.
In tenzro-sdk, web.signChallengeCredential(challenge, signer, payerDid, maxAmount) signs a credential for an MPP challenge with the account's hardware signer, client.payment.verifyPayment({ credential }) checks it, and client.payment.updatePaymentChannel({ voucher, charge }) pays a call on the channel.
5. Inspect channels and payments
tenzro escrow by-payer --payer 0x<address> --rpc https://rpc.tenzro.com
tenzro payment history --payer 0x<address> --rpc https://rpc.tenzro.comescrow by-payer lists the escrows, and so the channels, you have funded (JSON-RPC tenzro_listEscrowsByPayer).
Each settled payment is a consensus record naming the payer, payee, asset, amount and settlement fee; tenzro_getPayment reads one by its reference.
6. Settle in the asset you want
A provider chooses what it is paid in. keep_inbound keeps whatever asset the payer used, for example USDC; tnzo converts to TNZO. The change is signed by the provider's own identity key, so the node operator cannot change it on the provider's behalf. To check which networks and stablecoins a charge can settle on:
curl -s https://rpc.tenzro.com \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tenzro_settlementNetworks","params":{}}' | jq '.networks[] | {caip2, name, native_stablecoins, x402}'Next steps
- One-shot payments: Payments with x402.
- Stablecoins end to end: Pay for inference in stablecoins.
- Protocol reference: MPP.