Skip to content
v0.14.2Package availableIETF draft

MPP — Machine Payments Protocol

Machine Payments Protocol (MPP) defines HTTP payment authentication using theWWW-Authenticate: payment challenge scheme (draft-ryan-httpauth-payment). PEAC maps MPP payment challenges and receipts to portable signed records — the same way it handles x402.

Package: @peac/mappings-paymentauth

MPP and x402 — complementary payment rails

MPP and x402 solve similar problems — machine-to-machine HTTP payments — but with different wire protocols. PEAC evidence adapters exist for both. Use whichever rail your infrastructure supports; PEAC produces equivalent portable signed records for each.

DimensionMPP / paymentauthx402
HTTP mechanismWWW-Authenticate: payment (RFC 9110 auth framework)HTTP 402 Payment Required status code
Standard bodyIETF HTTP WG (draft-ryan-httpauth-payment)Linux Foundation (x402.org, 22+ members)
PEAC package@peac/mappings-paymentauth@peac/adapter-x402 + @peac/rails-x402
Challenge headerWWW-Authenticate: payment scheme=...X-Payment-Required or 402 body
Receipt headerAuthorization: payment token=...X-Payment / custom header
PEAC evidence typespayment-attempt-observed, payment-settlement-observedx402-offer-observed, x402-settlement-observed

Install

pnpm add @peac/mappings-paymentauth @peac/protocol

Server — parse and record the challenge

When your server issues an MPP payment challenge, record it as a PEAC evidence record. The challenge record proves what payment terms the server offered, independently of the upstream payment processor.

import { parsePaymentChallenge, mapChallengeToRecord } from '@peac/mappings-paymentauth';
import { issue } from '@peac/protocol';
import { loadKey } from '@peac/crypto';

const signingKey = await loadKey(process.env.PEAC_SIGNING_KEY_JSON!);

// Express middleware example
app.use(async (req, res, next) => {
  if (!req.headers.authorization) {
    // Parse your internal payment config into MPP challenge format
    const challenge = await paymentConfig.buildChallenge(req);

    // Map to PEAC record claims
    const record = mapChallengeToRecord({
      challenge,
      issuer: 'https://api.example.com',
      subject: req.headers['x-agent-id'] || 'unknown',
    });

    // Issue signed evidence of the challenge offered
    const { jws } = await issue({ claims: record, issuer: 'https://api.example.com', signingKey });

    // Return 401 with payment challenge + PEAC record ref in header
    res.set('WWW-Authenticate', `payment scheme="${challenge.scheme}" ...`);
    res.set('PEAC-Receipt', jws);
    return res.status(401).json({
      type: 'about:blank',
      title: 'Payment Required',
      status: 401,
    });
  }
  next();
});

Client — record payment attempt and settlement

When the agent pays and receives a response, record the payment attempt and settlement observation. The settlement record proves what the server attested — not more. PEAC does not synthesize finality from non-payment artifacts.

import {
  parsePaymentResponse,
  mapPaymentAttemptToRecord,
  mapSettlementToRecord,
} from '@peac/mappings-paymentauth';
import { issue, verifyLocal } from '@peac/protocol';
import { loadKey } from '@peac/crypto';

const signingKey = await loadKey(process.env.PEAC_SIGNING_KEY_JSON!);

// 1. Record the payment attempt
const attemptRecord = mapPaymentAttemptToRecord({
  scheme: 'mpp-v1',
  payment_ref: 'urn:payment:attempt:xyz',
  amount_minor: '100',
  currency: 'USD',
  target_ref: 'https://api.example.com/resource',
});

const { jws: attemptJws } = await issue({
  claims: attemptRecord,
  issuer: 'https://client.example.com',
  signingKey,
});

// 2. Make the actual HTTP call with payment token in Authorization
const response = await fetch('https://api.example.com/resource', {
  headers: {
    Authorization: `payment token="${paymentToken}"`,
    'PEAC-Receipt': attemptJws,
  },
});

// 3. If payment accepted, record settlement
if (response.ok) {
  const settlement = parsePaymentResponse(response.headers);

  const settlementRecord = mapSettlementToRecord({
    settlement,
    attempt_ref: 'urn:payment:attempt:xyz',
    upstream_artifact: { source: 'payment-server', ref: settlement.ref },
  });

  const { jws: settlementJws } = await issue({
    claims: settlementRecord,
    issuer: 'https://client.example.com',
    signingKey,
  });

  // settlementJws is your portable payment proof
  console.log('Payment settled:', settlementJws);
}

Evidence type URIs

Type URIWhen emittedKey fields
org.peacprotocol/payment-challenge-observedServer issues WWW-Authenticate: payment challengescheme, challenge_ref, amount_minor, currency
org.peacprotocol/payment-attempt-observedClient submits Authorization: payment tokenscheme, payment_ref, amount_minor, currency, target_ref
org.peacprotocol/payment-settlement-observedServer responds 200 with payment acceptedattempt_ref, outcome, upstream_artifact
org.peacprotocol/payment-error-observedServer rejects payment with a structured errorerror_code, error_ref, attempt_ref

MCP and JSON-RPC error helpers

When a paymentauth error occurs inside an MCP tool call or JSON-RPC handler, the error needs to be structured in a way that MCP clients can handle.@peac/mappings-paymentauth includes helpers for this.

import { buildPaymentauthMcpError, buildPaymentauthJsonRpcError } from '@peac/mappings-paymentauth';

// Inside an MCP tool handler
if (paymentRequired) {
  // Returns a structured MCP error object with payment challenge embedded
  return buildPaymentauthMcpError({
    challenge,
    message: 'Payment required to call this tool',
  });
}

// Inside a JSON-RPC handler
if (paymentRequired) {
  return buildPaymentauthJsonRpcError({
    challenge,
    code: -32099, // custom error code for payment required
  });
}

Payment finality rules

PEAC records what the upstream payment server attested — nothing more. A payment-settlement-observed record proves the server said "payment accepted". It does not guarantee funds cleared, chargeback immunity, or settlement finality at the rails level.

Never derive settlement finality from a challenge or attempt record alone. The mapSettlementToRecord function requires explicit upstream_artifact from the payment server — it will throw MapperBoundaryErrorif called with incomplete data.

Resources

Add payment evidence to your MPP implementation

@peac/mappings-paymentauth works alongside your existing MPP server implementation. No protocol changes required — just map each event to a PEAC record.