Skip to main content
x402 has two protocol versions. Version 2 is preferred for new integrations. Faremeter supports both versions simultaneously, and the middleware can serve v1 and v2 clients at the same time.

At a glance

v1v2
Client headerX-PAYMENTPAYMENT-SIGNATURE
Server requirement header(in response body only)PAYMENT-REQUIRED
Server response headerX-PAYMENT-RESPONSEPAYMENT-RESPONSE
Payload locationBase64-encoded JSON in headerBase64-encoded JSON in header
Payload includes acceptedNoYes — echoes the requirement the client chose
StatusSupportedPreferred

Version 1

In v1, the server returns payment requirements in the 402 response body as JSON:
{
  "x402Version": 1,
  "accepts": [
    {
      "scheme": "exact",
      "network": "solana-devnet",
      "asset": "USDC",
      "maxAmountRequired": "10000",
      "payTo": "7xKX..."
    }
  ]
}
The client sends its payment proof in the X-PAYMENT header:
X-PAYMENT: eyJ4NDAyVmVyc2lvbiI6MSwi...
The decoded payload contains:
{
  "x402Version": 1,
  "scheme": "exact",
  "network": "solana-devnet",
  "payload": { /* scheme-specific proof */ }
}

Version 2

v2 uses dedicated HTTP headers for the payment protocol, separating payment data from the response body. The server returns requirements in both the response body and the PAYMENT-REQUIRED header. On successful payment, it includes a PAYMENT-RESPONSE header with the settlement result. The client sends its payment proof in the PAYMENT-SIGNATURE header. The v2 payload includes the accepted requirement the client chose, plus resource information:
{
  "x402Version": 2,
  "payload": { /* scheme-specific proof */ },
  "resource": { "url": "https://api.example.com/data", "method": "GET" },
  "accepted": {
    "scheme": "exact",
    "network": "solana-devnet",
    "asset": "USDC",
    "maxAmountRequired": "10000",
    "payTo": "7xKX..."
  }
}
Including the accepted requirement in the payload lets the facilitator verify that the client is paying for the correct requirement without relying on the server to re-send it.

Configuring version support

The middleware uses resolveSupportedVersions() to determine which protocol versions to serve. By default, v1 is enabled and v2 is disabled.
import { resolveSupportedVersions } from "@faremeter/middleware/common"

// Default: v1 enabled, v2 disabled
const versions = resolveSupportedVersions()

// Enable both
const versions = resolveSupportedVersions({ x402v1: true, x402v2: true })

// v2 only
const versions = resolveSupportedVersions({ x402v1: false, x402v2: true })
Pass this to createMiddleware or handleMiddlewareRequest:

With createMiddleware (Express)

import { createMiddleware } from "@faremeter/middleware/express"

app.use(
  "/api/protected",
  await createMiddleware({
    facilitatorURL: "https://facilitator.corbits.dev",
    accepts,
    supportedVersions: resolveSupportedVersions({ x402v1: true, x402v2: true }),
  }),
)

With handleMiddlewareRequest

import { handleMiddlewareRequest, resolveSupportedVersions } from "@faremeter/middleware/common"

await handleMiddlewareRequest({
  facilitatorURL: "https://facilitator.corbits.dev",
  accepts,
  supportedVersions: resolveSupportedVersions({ x402v2: true }),
  resource: req.url,
  // ... other options
})

Migrating from v1 to v2

  1. Enable both versions during the transition so existing v1 clients continue to work:
    resolveSupportedVersions({ x402v1: true, x402v2: true })
    
  2. Update clients to use the latest @faremeter/rides or @faremeter/fetch, which automatically negotiate the highest supported version.
  3. Disable v1 once all clients have migrated:
    resolveSupportedVersions({ x402v1: false, x402v2: true })
    

Version adapters

If you are running your own facilitator and need to convert between v1 and v2 formats, use the adapter functions from @faremeter/types/x402-adapters:
FunctionDirection
adaptPayloadV1ToV2()v1 payment payload to v2
adaptPaymentRequiredResponseV1ToV2()v1 requirements response to v2
adaptSettleResponseV1ToV2()v1 settle response to v2
adaptSettleResponseV2ToV1()v2 settle response to v1
adaptSettleResponseV2ToV1Legacy is deprecated. Use adaptSettleResponseV2ToV1 for spec-compliant v1 output.

Further reading