Skip to main content
This guide explains the end-to-end x402 flow from both the subscriber (client) and resource server (API/agent) perspectives.
For the complete technical specification, see the x402 Smart Accounts Extension Spec.
If you’re new to the programmable extension concepts, see:

The Nevermined x402 programmable extension

Nevermined extends x402 with the nvm:erc4337 scheme, enabling programmable settlement (credits/subscriptions/PAYG) using ERC-4337 smart accounts and session keys.

PaymentRequired Response (402)

When a server requires payment, it returns a 402 response with a payment-required header:
{
  "x402Version": 2,
  "error": "Payment required to access resource",
  "resource": {
    "url": "/api/v1/agents/80918427.../tasks",
    "description": "AI agent task execution"
  },
  "accepts": [{
    "scheme": "nvm:erc4337",
    "network": "eip155:84532",
    "planId": "44742763076047497640080230236781474129970992727896593861997347135613135571071",
    "extra": {
      "version": "1",
      "agentId": "80918427023170428029540261117198154464497879145267720259488529685089104529015"
    }
  }],
  "extensions": {}
}

PaymentPayload (Client Response)

The client responds with a payment-signature header containing the x402 access token:
{
  "x402Version": 2,
  "accepted": {
    "scheme": "nvm:erc4337",
    "network": "eip155:84532",
    "planId": "44742763076047497640080230236781474129970992727896593861997347135613135571071",
    "extra": {
      "version": "1",
      "agentId": "80918427023170428029540261117198154464497879145267720259488529685089104529015"
    }
  },
  "payload": {
    "signature": "0x01845ADb2C711129d4f3966735eD98a9F09fC4cE...",
    "authorization": {
      "from": "0xD4f58B60330bC59cB0A07eE6A1A66ad64244eC8c",
      "sessionKeysProvider": "zerodev",
      "sessionKeys": [
        { "id": "order", "data": "0x20a13d82dd9ee289..." },
        { "id": "redeem", "data": "0x68e8e34d65914908..." }
      ]
    }
  },
  "extensions": {}
}

Subscriber flow (client side)

Step 1: Discover payment requirements (HTTP 402)

When calling a protected endpoint, the server returns a 402 Payment Required response with the payment-required header containing the payment requirements.
import { X402_HEADERS } from '@nevermined-io/payments/express'

// Call protected endpoint - get 402 response
const response = await fetch('https://api.example.com/protected')

if (response.status === 402) {
  // Decode the payment-required header
  const paymentRequired = JSON.parse(
    Buffer.from(response.headers.get(X402_HEADERS.PAYMENT_REQUIRED)!, 'base64').toString()
  )

  // Extract planId and agentId from accepts array
  const { planId, extra } = paymentRequired.accepts[0]
  const agentId = extra?.agentId
}

Step 2: Generate x402 access token

Use the Nevermined SDK to generate an x402 access token with the required session keys:
import { Payments } from '@nevermined-io/payments'

const payments = Payments.getInstance({
  nvmApiKey: process.env.NVM_API_KEY,
  environment: 'sandbox'
})

// Generate the x402 access token
const { accessToken } = await payments.x402.getX402AccessToken(planId, agentId)

Step 3: Retry request with payment header

Send the x402 access token in the payment-signature header:
import { X402_HEADERS } from '@nevermined-io/payments/express'

const result = await fetch('https://api.example.com/protected', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    [X402_HEADERS.PAYMENT_SIGNATURE]: accessToken
  },
  body: JSON.stringify({ prompt: 'Hello' })
})

// Decode the settlement receipt from payment-response header
if (result.ok) {
  const settlement = JSON.parse(
    Buffer.from(result.headers.get(X402_HEADERS.PAYMENT_RESPONSE)!, 'base64').toString()
  )
  console.log('Credits used:', settlement.creditsRedeemed)
}

Resource server flow (API/agent side)

Recommended: For Express.js applications, use the paymentMiddleware which handles all of this automatically with one line of code.

Step 1: Return 402 when payment is missing

If the payment header is not present, respond with 402 and set the payment-required header with your payment requirements:
import { X402_HEADERS } from '@nevermined-io/payments/express'

const x402Token = req.headers['payment-signature']

if (!x402Token) {
  const paymentRequired = {
    x402Version: 2,
    error: 'Payment required to access resource',
    resource: { url: req.path },
    accepts: [{
      scheme: 'nvm:erc4337',
      network: 'eip155:84532',
      planId: process.env.NVM_PLAN_ID,
      extra: {
        version: '1',
        agentId: process.env.NVM_AGENT_ID
      }
    }],
    extensions: {}
  }

  res.set(X402_HEADERS.PAYMENT_REQUIRED, Buffer.from(JSON.stringify(paymentRequired)).toString('base64'))
  return res.status(402).json({ error: 'Payment Required' })
}

Step 2: Verify with the facilitator

Verify the x402 token with the facilitator. The facilitator extracts planId and subscriber address from the token:
import { Payments } from '@nevermined-io/payments'

const payments = Payments.getInstance({
  nvmApiKey: process.env.NVM_API_KEY,
  environment: 'sandbox'
})

// Verify with facilitator - extracts planId/subscriberAddress from token
const verification = await payments.facilitator.verifyPermissions({
  x402AccessToken: x402Token,
  maxAmount: BigInt(creditsRequired)
})

if (!verification.isValid) {
  return res.status(402).json({ error: 'Payment verification failed' })
}

Step 3: Execute Workload

// Only execute after verification succeeds
const result = await processAIRequest(req.body)

Step 4: Settle Payment

Settle after work is complete and return the settlement receipt in the payment-response header:
import { X402_HEADERS } from '@nevermined-io/payments/express'

// Settle after work is complete
const settlement = await payments.facilitator.settlePermissions({
  x402AccessToken: x402Token,
  maxAmount: BigInt(actualCreditsUsed)
})

// Set payment-response header with settlement receipt
const settlementReceipt = {
  success: true,
  creditsRedeemed: actualCreditsUsed,
  transactionHash: settlement.txHash
}
res.set(X402_HEADERS.PAYMENT_RESPONSE, Buffer.from(JSON.stringify(settlementReceipt)).toString('base64'))

// Return response
res.json({ result })

Complete Request Lifecycle

Error Handling

ErrorHTTP StatusCause
Missing payment header402No payment-signature header
Invalid signature402Signature verification failed
Insufficient balance402User needs to purchase more credits
Expired session402Session key has expired
Settlement failed500On-chain transaction failed

Next Steps