MPP — Machine Payments Protocol
Stripe's machine-to-machine payment protocol over HTTP 402.
Stripe's machine-to-machine payment protocol over HTTP 402. The agent declares its accepted payment rails (card via Stripe / Visa SPTs, stablecoins via Tempo, Lightning), the spending limit it's authorised for, and the facilitator endpoint it talks to.
Stripe-issued payment intent + agent verification handshake: AstraSync sits between the agent and Stripe.
You declare: merchant ID, facilitator URL, payment methods, currency, spending limit.
PDLSS pre-fill: spending limit flows to autonomous and hard limits; facilitator domain +
merchant:<id> added to counterparties; currency mirrored to PDLSS.
{
"protocols": ["a2a", "mpp"],
"mpp": {
"merchantId": "merchant_abc123",
"facilitatorUrl": "https://mpp.example.com",
"paymentMethods": ["card", "stablecoin"],
"currency": "USD",
"spendingLimit": 500
}
}Verifying the paid leg
When the agent retries with Authorization: Payment …, the gateway SDK forwards the MPP request
to verify-access. verify-access checks:
- the challenge's expiry (with 60 seconds of clock skew);
- the body digest, when the raw request body is forwarded: the challenge must carry a
sha-256=digest that matches the bytes; - replay: the same challenge nonce and digest are accepted once within the window.
It does not verify the challenge's HMAC binding (that needs your secret) or the payment proof
itself. That is your job: call the SDK's verifyMppChallenge before you fulfil. For tempo and
stripe credentials, verify-access records a protocol_native order in pending_merchant, and
you report the settlement once you have taken the payment.
Keeping the route purpose (5.19.0)
The express and Next.js middlewares send purposeSource with requestMethod and requestPath
on every verify-access call. When a dashboard route rule set the purpose (e.g.
POST /api/checkout/* → shopping.purchase), verify-access keeps it on the paid leg instead of
replacing it with commerce.payment.execute, after checking that a stored rule on your
endpoint matches that method and path with the same purpose and action. The claim is checked,
never trusted; otherwise the commerce purpose applies as before. The paid leg never records a
second order.
The gateway forwards the exact request bytes when your JSON parser keeps them on req.rawBody (a
Buffer or string), so body digests match without mounting express.raw or reordering parsers:
app.use(express.json({ verify: (req, _res, buf) => ((req as any).rawBody = buf) }));On SDK 5.18.x, put the paid leg on a route marked observe until you upgrade.

