TypeScript implementation of the "Payment" HTTP Authentication Scheme (402 Protocol).
mppx provides abstractions for the complete HTTP 402 payment flow — both client and server. The architecture has two layers:
-
Mppx— Top-level payment handler. Groups relatedMethods and handles the HTTP 402 flow (challenge/credential parsing, header serialization, verification). -
Method— A payment method definition. Each method has aname(e.g.,charge,session), amethod(e.g.,tempo,stripe), and schemas for request validation and credential payloads.
┌────────────────────┐ ┌────────────────┐
│ Mppx │ 1 * │ Method │
│ (handler) ├───────┤ (definition) │
└────────────────────┘ has └────────────────┘
│ payment │ │ tempo/charge │
│ │ │ tempo/session │
│ │ │ stripe/charge │
└────────────────────┘ └────────────────┘
Client (Mppx) Server (Mppx)
│ │
│ (1) GET /resource │
├──────────────────────────────────────────────────>│
│ │
│ (2) handler.charge(request, { ... }) │
│ 402 + WWW-Authenticate: Payment │
│<──────────────────────────────────────────────────┤
│ │
│ (3) handler.createCredential(response) │
│ │
│ (4) GET /resource │
│ Authorization: Payment <credential> │
├──────────────────────────────────────────────────>│
│ │
│ (5) handler.charge(request) │
│ │
│ (6) 200 OK │
│ Payment-Receipt: <receipt> │
│<──────────────────────────────────────────────────┤
│ │
Low-level data structures that compose into the core abstractions:
Challenge— Server-issued payment request (appears inWWW-Authenticateheader). Containsid,realm,method,intent,request, and optionalexpires/digest.Credential— Client-submitted payment proof (appears inAuthorizationheader). Containschallengeecho,payload(method-specific proof), and optionalsource(payer identity).Method— Payment method definition (e.g.,tempo/charge,stripe/charge). Containsmethod,name, and validatedschema(credential payload + request).Mppx— Top-level payment handler. Groups relatedMethods and handles the HTTP 402 flow.Receipt— Server-issued settlement confirmation (appears inPayment-Receiptheader). Containsstatus,method,timestamp, andreference.Request— Method-specific payment parameters (e.g.,amount,currency,recipient). Validated by the method's schema and serialized in the challenge.
Methods are flat structural types with method, name, and schema:
const tempoCharge = Method.from({
method: 'tempo',
name: 'charge',
schema: {
credential: {
payload: z.object({ signature: z.string(), type: z.literal('transaction') }),
},
request: z.pipe(
z.object({
amount: z.amount(),
chainId: z.optional(z.number()),
currency: z.string(),
decimals: z.number(),
recipient: z.optional(z.string()),
// ...
}),
z.transform(({ amount, decimals, chainId, ... }) => ({
amount: parseUnits(amount, decimals).toString(),
...(chainId !== undefined ? { methodDetails: { chainId } } : {}),
})),
),
},
})Methods are extended with client or server logic via Method.toClient() and Method.toServer():
// Client-side: adds credential creation
const client = Method.toClient(Methods.charge, {
async createCredential({ challenge }) { ... },
})
// Server-side: adds verification
const server = Method.toServer(Methods.charge, {
async verify({ credential }) { ... },
})Canonical specs live at tempoxyz/payment-auth-spec.
| Layer | Spec | Description |
|---|---|---|
| Core | draft-httpauth-payment-00 | 402 flow, WWW-Authenticate/Authorization headers, Payment-Receipt |
| Intent | draft-payment-intent-charge-00 | One-time immediate payment |
| Intent | draft-payment-intent-session-00 | Pay-as-you-go streaming payments |
| Method | draft-tempo-charge-00 | TIP-20 token transfers on Tempo |
| Method | draft-tempo-session-00 | Tempo payment channels for streaming |
| Extension | draft-payment-discovery-00 | /.well-known/payment discovery |
- Challenge:
WWW-Authenticate: Payment id="...", realm="...", method="...", intent="...", request="<base64url>" - Credential:
Authorization: Payment <base64url>→{ challenge, payload, source? } - Receipt:
Payment-Receipt: <base64url>→{ status, method, timestamp, reference } - Encoding: All JSON payloads use base64url without padding (RFC 4648)
The challenge id is an HMAC-SHA256 over the challenge parameters, cryptographically binding the ID to its contents. This prevents tampering and ensures the server can verify challenge integrity without storing state.
HMAC input (concatenated, pipe-delimited):
realm | method | intent | request | expires | digest
Generation:
id = base64url(HMAC-SHA256(server_secret, input))
Verification: Server recomputes HMAC from echoed challenge parameters and compares to id. If mismatch, reject credential.
pnpm build # Build with zile
pnpm check # Lint and format with biome
pnpm check:types # TypeScript type checking
pnpm test # Run tests with vitestLoad these skills for specialized guidance:
Use when: Implementing payment methods, understanding the 402 protocol flow, working with Tempo/Stripe payment method schemas, or referencing the IETF spec.
Use when: Building new modules, structuring exports, or following library patterns.
Use when: Writing or reviewing TypeScript code for style and conventions.
Use when: Referencing Tempo protocol specifics, understanding TIP-20 tokens, Tempo transactions (0x76), or protocol-level details.