Skip to content
LogoLogo

Add payments to your API

Charge for access to protected resources

Overview

This quickstart demonstrates how to plug MPP into any server framework to accept payments for protected resources. Pick the path that suits you:

  • Prompt mode: paste a prompt into your coding agent and build in one prompt
  • Framework mode: use mppx middleware for Next.js, Hono, Elysia, or Express
  • Manual mode: call mppx/server directly with the Fetch API

Prompt mode

Paste this into your coding agent to set up a server with mppx in one prompt:

Framework mode

Use the framework-specific middleware from mppx to integrate payment into your server. Each middleware handles the 402 Challenge/Credential flow and attaches receipts automatically.

import { Mppx, tempo } from 'mppx/nextjs'
 
const mppx = Mppx.create({
  methods: [tempo({
    currency: '0x20c0000000000000000000000000000000000000', // pathUSD on Tempo
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})
 
export const GET = 
  mppx.charge({ amount: '0.1' }) 
  (() => Response.json({ data: '...' }))

Advanced: manual mode

If you prefer full control over the payment flow, use mppx/server directly with the Fetch API.

import { Mppx, tempo } from 'mppx/server'
 
const mppx = Mppx.create({
  methods: [tempo({
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})
 
export async function handler(request: Request) { 
  const response = await mppx.charge({ amount: '0.1' })(request) 
 
  // Payment required: send 402 response with challenge 
  if (response.status === 402) return response.challenge 
 
  // Payment verified: attach receipt and return resource 
  return response.withReceipt(Response.json({ data: '...' })) 
} 

The intent handler accepts a Fetch API-compatible request object, and returns a Response object.

The Fetch API is compatible with most server frameworks, including: Hono, Deno, Cloudflare Workers, Next.js, Bun, and other Fetch API-compatible frameworks.

Node.js & Express compatibility

If your framework doesn't support the Fetch API (for example, Express or Node.js), you're likely interfacing with the Node.js Request Listener API.

Use the Mppx.toNodeListener helper to transform the handler into a Node.js-compatible listener.

export async function (: , : ) { 
  const  = await .( 
    .({ : '0.1' })
  )(, ) 
 
  // Payment required: send 402 response with challenge 
  if (. === 402) return . 
 
  // Payment verified: attach receipt and return resource 
  return .(.({ : '...' })) 
} 

Push & pull modes

Non-zero Tempo charges support two transaction submission modes, determined by the client. Zero-amount charges skip transaction submission entirely and use a proof Credential payload instead.

  • pull mode (default): the client signs the transaction and sends the serialized transaction to the server. The server broadcasts it and verifies on-chain. This enables the server to sponsor gas fees via a feePayer.
  • push mode: the client builds, signs, and broadcasts the transaction itself (for example, via a browser wallet). It sends the transaction hash to the server, which verifies the payment by fetching the receipt.

Your server handles all three payload types automatically—no configuration required. The server inspects the credential payload type (proof for zero-amount Challenges, transaction for pull, hash for push) and verifies accordingly.

If you would like to force a specific mode, you can set the mode parameter to 'pull' or 'push'.

import { Mppx, tempo } from 'mppx/server'
 
const mppx = Mppx.create({
  methods: [tempo({
    currency: '0x20c0000000000000000000000000000000000000',
    mode: 'push', 
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

The mode parameter only affects non-zero charges. When amount is 0, the client always returns a proof payload and feePayer is irrelevant.

Fee sponsorship

To sponsor gas fees for pull-mode clients, pass a feePayer account to tempo():

import { Mppx, tempo } from 'mppx/server'
import { privateKeyToAccount } from 'viem/accounts'
 
const mppx = Mppx.create({
  methods: [tempo({
    currency: '0x20c0000000000000000000000000000000000000',
    feePayer: privateKeyToAccount('0x…'), 
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

It is possible to pass a fee payer service URL instead:

import { Mppx, tempo } from 'mppx/server'
 
const mppx = Mppx.create({
  methods: [tempo({
    currency: '0x20c0000000000000000000000000000000000000',
    feePayer: 'https://sponsor.example.com', 
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})

When a pull-mode client submits a signed transaction, the server co-signs with the fee payer account (or delegates to the relay) before broadcasting. Push-mode clients pay their own gas, so feePayer is ignored for those requests. Zero-amount proof flows do not create a transaction at all.

Optimistic verification

By default, the server waits for onchain confirmation before returning a Receipt. For lower latency, set waitForConfirmation: false to return immediately after simulation:

const mppx = Mppx.create({
  methods: [tempo({
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    waitForConfirmation: false, 
  })],
})

Discovery

After your server is running, add discovery so agents can find your API and its payment terms automatically. The discovery() helper generates a GET /openapi.json endpoint from your route configuration:

server.ts
import { Hono } from 'hono'
import { Mppx, discovery } from 'mppx/hono'
import { tempo } from 'mppx/server'
 
const app = new Hono()
 
const mppx = Mppx.create({
  methods: [tempo({
    currency: '0x20c0000000000000000000000000000000000000',
    recipient: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  })],
})
 
app.get('/resource', mppx.charge({ amount: '0.1' }), (c) => c.json({ data: '...' }))
 
discovery(app, mppx, {
  auto: true,
  info: { title: 'My API', version: '1.0.0' },
})

The generated document advertises each paid route with canonical x-payment-info.offers[] entries. See Discovery for the full document shape, multi-offer examples, and flat-shorthand compatibility notes.

Register your service on MPPScan or the MPP Services directory so agents and registries can discover it.

Testing your server

After your server is running, test it with the mppx CLI:

terminal
# Create an account funded with testnet tokens
$ npx mppx account create
 
# Make a paid request
$ npx mppx <your-server>/resource

Next steps