Skip to content

MPP

The Machine Payments Protocol (MPP) lets a server answer a request with a 402 challenge. The client pays, by Stripe Shared Payment Token or by a stablecoin transfer, and retries with a credential. The server returns a receipt.

HalvorAI records MPP from the server that is being paid, by wrapping the mppx charge handler.

import { Mppx } from 'mppx/server';
import { HalvorClient } from '@halvorai/ingest';
import { withHalvorCharge } from '@halvorai/ingest/mpp';
const halvor = new HalvorClient();
const mppx = Mppx.create({ /* your methods and secret */ });
const charge = withHalvorCharge(mppx.charge({ amount: '0.50' }), halvor);
export async function handle(request: Request): Promise<Response> {
const result = await charge(request);
if (result.status === 402) return result.challenge; // the client will pay and retry
return new Response('the paid resource');
}

withHalvorCharge returns a handler with the same signature as the one you pass in. It does not change the result, and it never throws.

MomentEvent
The server issues a 402 challengepayment.requested
A charge succeeds and a receipt is returnedpayment.settled, with the receipt attached as mpp.receipt evidence
MPPAPE
The payment method (spt or tempo)payment.rail_ref.method, with payment.rail set to mpp
The challenge idpayment.rail_ref.challenge_id
The Stripe PaymentIntent behind an SPT chargepayment.rail_ref.payment_intent
A Tempo transfer hashpayment.rail_ref.tx_hash, with the amount in atomic units

An MPP server usually does not know the agent that is paying. Supply what you know, either per request:

await withAgentContext({ agent: { id: payerId }, owner: { id: accountId, type: 'business' } }, () => handle(request));

or leave it to default to unattributed, which still records the payment but lowers your attribution coverage.

Run the MPP validator against your endpoint, then open Payments. You should see a payment.requested event followed by a payment.settled event with the receipt in its evidence.