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

The Nevermined x402 programmable extension

Nevermined extends x402 with a smart-account-friendly scheme so the payment payload can authorize programmable settlement (credits/subscriptions/PAYG) instead of only an ERC-20 transfer.
{
  "x402Version": 2,
  "scheme": "contract",
  "network": "base-sepolia",
  "extensions": {
    "nevermined": {
      "info": {
        "plan_id": "did:nv:plan-123",
        "agent_id": "did:nv:agent-456",
        "max_amount": "1000000",
        "network": "base-sepolia"
      }
    }
  },
  "payload": {
    "session_key": "<delegated-session-key-or-proof>"
  }
}

Subscriber flow (client side)

Step 1: Discover payment requirements (HTTP 402)

When calling a protected endpoint, the server returns a 402 Payment Required response and includes the payment requirements (plan/agent binding, maximum amount, network, scheme).
// Call protected endpoint - get 402 response
const response = await fetch('https://api.example.com/protected')

if (response.status === 402) {
  const paymentRequired = await response.json()
  const nvmInfo = paymentRequired.extensions.nevermined.info
  // nvmInfo.plan_id, nvmInfo.agent_id, nvmInfo.max_amount, nvmInfo.network
}

Step 2: Build the x402 payment payload

The client creates a payment payload that encodes the allowed permissions (for example, a session-key capability scoped to a plan/agent and a spend cap).
import { Payments } from '@nevermined-io/payments'

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

const { plan_id, agent_id, network } = paymentRequired.extensions.nevermined.info

// Create a delegated permission (session key / proof) the facilitator can verify.
// The exact mechanics depend on the plan model and scheme (see the blog posts above).
const { accessToken } = await payments.x402.getX402AccessToken(plan_id, agent_id)

const paymentPayload = {
  x402Version: 2,
  scheme: 'contract',
  network,
  payload: { session_key: accessToken },
  extensions: paymentRequired.extensions
}

Step 3: Retry request with payment header

The payment payload is sent in-band with HTTP (commonly via the PAYMENT-SIGNATURE header).
const encodedPayload = Buffer.from(JSON.stringify(paymentPayload)).toString('base64')

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

Resource server flow (API/agent side)

Step 1: Return 402 when payment is missing

If the payment header is not present, respond with 402 plus the payment requirements you accept.
const paymentHeader = req.headers['payment-signature']

if (!paymentHeader) {
  return res.status(402).json({
    error: 'Payment Required',
    x402Version: 2,
    extensions: {
      nevermined: {
        info: {
          plan_id: process.env.PLAN_ID,
          agent_id: process.env.AGENT_ID,
          max_amount: '1000000',
          network: 'base-sepolia'
        }
      }
    }
  })
}

Step 2: Verify with the facilitator

Decode the payment payload and ask the facilitator to verify it (including simulating any allowed on-chain actions).
import { Payments } from '@nevermined-io/payments'

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

// Decode and parse the payment payload
const paymentPayload = JSON.parse(
  Buffer.from(paymentHeader, 'base64').toString()
)

const { plan_id, max_amount } = paymentPayload.extensions.nevermined.info

// Verify with facilitator
const verification = await payments.facilitator.verifyPermissions({
  planId: plan_id,
  maxAmount: BigInt(max_amount),
  x402AccessToken: paymentPayload.payload.session_key,
  subscriberAddress: req.body.subscriber_address
})

if (!verification.success) {
  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
const settlement = await payments.facilitator.settlePermissions({
  planId: plan_id,
  maxAmount: BigInt(max_amount),
  x402AccessToken: paymentPayload.payload.session_key,
  subscriberAddress: req.body.subscriber_address
})

// Return response with transaction hash
res.json({
  result,
  receipt: {
    txHash: settlement.transactionHash,
    creditsCharged: settlement.amount
  }
})

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