Skip to main content
@faremeter/middleware adds payment walls to your HTTP server. It supports both x402 and MPP protocols. When a request hits a protected route, the middleware checks for a valid payment header. If missing, it returns a payment challenge. If present, it verifies the payment through a handler and allows the request to proceed.
pnpm add @faremeter/middleware

How it works

  1. A request arrives at a protected endpoint.
  2. The middleware checks for a payment header (X-PAYMENT or PAYMENT-SIGNATURE).
  3. If missing: returns 402 Payment Required with the configured requirements.
  4. If present: sends the payment to the facilitator for verification and settlement.
  5. If the facilitator confirms: the request continues to your handler.
  6. If the facilitator rejects: returns an error response.

Framework support

FrameworkImport path
Express@faremeter/middleware/express
Hono@faremeter/middleware/hono
Custom@faremeter/middleware/common
The common module provides the core payment verification logic without framework bindings. Use it to integrate with any HTTP server.

Configuration

The middleware supports two configuration modes: in-process handlers and remote facilitator. Both are configured through CommonMiddlewareArgs. The two modes are mutually exclusive for x402 — pass either x402Handlers/pricing or facilitatorURL/accepts, not both. You can combine remote x402 handlers with in-process MPP handlers using createRemoteX402Handlers.

In-process handlers

Pass payment handlers directly to the middleware. The middleware calls them locally to resolve requirements, verify, and settle payments — no separate facilitator service needed. See the Facilitator guide for how to create handlers.
import { createMiddleware } from "@faremeter/middleware/hono"

const middleware = await createMiddleware({
  x402Handlers: [solanaHandler, evmHandler],
  mppMethodHandlers: [mppChargeHandler],
  pricing: [
    { amount: "10000", asset: "USDC", recipient: "7xKX...", network: "solana:devnet" },
    { amount: "10000", asset: "USDC", recipient: "0x1234...", network: "eip155:84532" },
  ],
})
OptionTypeDescription
x402HandlersFacilitatorHandler[]Optional. x402 payment handlers for in-process settlement.
mppMethodHandlersMPPMethodHandler[]Optional. MPP method handlers for in-process settlement.
pricingResourcePricing[]What to charge. Required when using in-process handlers.
supportedVersionsSupportedVersionsConfigOptional. Which x402 protocol versions to serve (v1, v2, or both). Defaults to v1 only.
ResourcePricing is the resource server’s statement of “I want X amount of Y asset paid to Z recipient on W network.” It says nothing about x402 schemes or protocol extras — those are determined by the handlers. The recipient field maps to payTo in the underlying x402PaymentRequirements that the handler produces.

Remote facilitator

Communicate with a separate facilitator service over HTTP for x402 payments. This is the original configuration model and is still fully supported.
import { createMiddleware } from "@faremeter/middleware/hono"

const middleware = await createMiddleware({
  facilitatorURL: "https://facilitator.corbits.dev",
  accepts: [
    // Payment requirements
  ],
  cacheConfig: {
    // Optional LRU cache settings
  },
})
OptionTypeDescription
facilitatorURLstringURL of the facilitator server.
acceptsx402PaymentRequirements[]Payment methods this endpoint accepts.
cacheConfigobjectOptional cache configuration for facilitator responses.
supportedVersionsSupportedVersionsConfigOptional. Which x402 protocol versions to serve (v1, v2, or both). Defaults to v1 only. See How x402 Works.

Mixing remote x402 with in-process MPP

Use createRemoteX402Handlers to wrap a remote facilitator as in-process handlers, then combine with MPP handlers under a shared pricing config:
import { createMiddleware } from "@faremeter/middleware/hono"
import { createRemoteX402Handlers } from "@faremeter/middleware"

const x402Handlers = createRemoteX402Handlers({
  facilitatorURL: "https://facilitator.corbits.dev",
  accepts: [/* x402 payment requirements */],
})

const middleware = await createMiddleware({
  x402Handlers,
  mppMethodHandlers: [mppChargeHandler],
  pricing: [
    { amount: "10000", asset: "USDC", recipient: "7xKX...", network: "solana:devnet" },
  ],
})

Defining payment requirements

Use @faremeter/info to construct payment requirements:
import { x402Exact } from "@faremeter/info/solana"

const accepts = [
  x402Exact({
    network: "devnet",
    payTo: "7xKXwxRPMo2sUAT5...",
    asset: "USDC",
    amount: "10000",
  }),
]
You can accept multiple payment methods by passing an array. The Solana x402Exact returns an array (one entry per legacy network ID), while the EVM x402Exact returns a single object. The middleware’s accepts field supports nested arrays, so both can be passed directly:
import { x402Exact as solanaX402Exact } from "@faremeter/info/solana"
import { x402Exact } from "@faremeter/info/evm"

const accepts = [
  solanaX402Exact({ network: "devnet", payTo: "7xKX...", asset: "USDC", amount: "10000" }),
  x402Exact({ network: "base-sepolia", payTo: "0x1234...", asset: "USDC", amount: "10000" }),
]

Caching

The middleware caches facilitator /accepts responses using an LRU cache with TTL. This reduces the number of requests to the facilitator. Cache configuration is optional.

Dynamic pricing

With in-process handlers, dynamic pricing happens naturally. When the middleware calls a handler’s getRequirements method, it passes the resource URL and payment requirements as context. The handler can inspect the resource and return different prices per request. You configure base pricing via ResourcePricing and let the handler adjust. For the remote facilitator path, or when you need full control over per-request pricing from the middleware layer, use handleMiddlewareRequest.

handleMiddlewareRequest — the lower-level API

createMiddleware (used by the Express and Hono integrations) accepts static configuration at initialization. When you need to compute requirements per-request — for example, varying the price based on a query parameter, header, or request body — use handleMiddlewareRequest from @faremeter/middleware/common. The tradeoff is that you wire up the framework integration yourself: reading headers, sending JSON responses, and calling your route handler.
import {
  handleMiddlewareRequest,
  resolveSupportedVersions,
} from "@faremeter/middleware/common"

When to use which

createMiddlewarehandleMiddlewareRequest
PricingFixed per routeDynamic per request
FrameworkExpress or HonoAny HTTP server
SetupOne-liner middlewareManual header/response wiring
Use caseMost endpointsDynamic pricing, custom frameworks

Signature

handleMiddlewareRequest accepts a single options object:
OptionTypeDescription
x402HandlersFacilitatorHandler[] (optional)x402 payment handlers.
mppMethodHandlersMPPMethodHandler[] (optional)MPP method handlers.
pricingResourcePricing[]Required. Payment pricing for this request.
resourcestringThe URL being accessed (typically req.url).
supportedVersionsRequired<SupportedVersionsConfig>Which x402 protocol versions to support. Use resolveSupportedVersions().
getHeader(key: string) => string | undefinedRead a request header.
sendJSONResponse(status, body, headers?) => ResponseSend a JSON response with status code and optional headers.
setResponseHeader(key, value) => void (optional)Set a response header.
getBody() => Promise<ArrayBuffer | null> (optional)Read the request body (for MPP digest binding).
body(ctx: MiddlewareBodyContext) => Promise<Response | void>Called when a valid payment is received.

The body callback

The body function runs when the client sends a valid payment. It receives a context object with a protocolVersion discriminant: x402 v1 context (protocolVersion: 1):
  • settle(), verify(), paymentPayload, paymentRequirements
x402 v2 context (protocolVersion: 2):
  • settle(), verify(), paymentPayload, paymentRequirements
MPP context (protocolVersion: "mpp"):
  • settle(), credential
Use protocolVersion to narrow the context type before accessing protocol-specific fields:
body: async (context) => {
  const result = await context.settle()
  if (!result.success) {
    return result.errorResponse
  }
  // Proceed to route handler
}

Example: Hono with dynamic pricing

import { Hono, type MiddlewareHandler } from "hono"
import {
  handleMiddlewareRequest,
  resolveSupportedVersions,
} from "@faremeter/middleware/common"
import { createRemoteX402Handlers } from "@faremeter/middleware"
import { x402Exact } from "@faremeter/info/evm"

const dynamicPricing = (): MiddlewareHandler => {
  return async (c, next) => {
    const amount = c.req.query("amount") ?? "10000"

    const x402Handlers = createRemoteX402Handlers({
      facilitatorURL: "https://facilitator.corbits.dev",
      accepts: [
        x402Exact({
          network: "base-sepolia",
          asset: "USDC",
          amount,
          payTo: process.env.MERCHANT_ADDRESS,
        }),
      ],
    })

    return await handleMiddlewareRequest({
      x402Handlers,
      pricing: [
        { amount, asset: "USDC", recipient: process.env.MERCHANT_ADDRESS, network: "eip155:84532" },
      ],
      supportedVersions: resolveSupportedVersions(),
      resource: c.req.url,
      getHeader: (key) => c.req.header(key),
      sendJSONResponse: (status, body, headers) => {
        if (headers) {
          for (const [key, value] of Object.entries(headers)) {
            c.header(key, value)
          }
        }
        c.status(status)
        return c.json(body)
      },
      body: async (context) => {
        const result = await context.settle()
        if (!result.success) return result.errorResponse
        await next()
      },
    })
  }
}
See the Dynamic Pricing recipe for a complete multi-chain example with client code.

Framework-specific guides

  • Express — Express middleware setup.
  • Hono — Hono middleware setup.
  • Facilitator — Running your own facilitator.