Documentation Index
Fetch the complete documentation index at: https://docs.nevermined.app/llms.txt
Use this file to discover all available pages before exploring further.
Validation of Requests
This guide explains how AI agents validate incoming requests and settle payments using the Nevermined Facilitator. Agent builders use these methods to verify subscriber access and burn credits.
Overview
The validation flow consists of:
- Extract Token: Read X402 access token from request headers
- Build Payment Required: Create payment requirement specification
- Verify Permissions: Check if subscriber has valid access
- Execute Task: Process the agent request
- Settle Permissions: Burn credits after successful execution
- Return Response: Send result or 402 error to subscriber
Receiving Requests
Extract the X402 access token from request headers. The X402 v2 spec defines the PAYMENT-SIGNATURE header:
import express from 'express'
const app = express()
app.use(express.json())
app.post('/api/v1/tasks', async (req, res) => {
// Extract token from PAYMENT-SIGNATURE header (X402 v2)
const accessToken = req.headers['payment-signature'] as string
// Alternative: Authorization header
// const accessToken = req.headers.authorization?.replace('Bearer ', '')
if (!accessToken) {
return res.status(402).json({ error: 'Payment required' })
}
// Process request...
})
Build Payment Required
Use the buildPaymentRequired helper to create the payment specification:
import { buildPaymentRequired } from '@nevermined-io/payments'
const paymentRequired = buildPaymentRequired(planId, {
endpoint: req.url, // Resource URL
agentId: agentId, // Your agent ID
httpVerb: req.method, // HTTP method (POST, GET, etc.)
network: 'eip155:84532', // Optional: blockchain network
description: 'Task API' // Optional: description
})
Verify Permissions
Before executing the request, verify the subscriber has valid access:
import { Payments, EnvironmentName } from '@nevermined-io/payments'
import { buildPaymentRequired } from '@nevermined-io/payments'
const agentPayments = Payments.getInstance({
nvmApiKey: process.env.NVM_API_KEY!, // Builder/agent API key
environment: 'sandbox' as EnvironmentName,
})
app.post('/api/v1/tasks', async (req, res) => {
const accessToken = req.headers['payment-signature'] as string
if (!accessToken) {
return res.status(402).json({ error: 'Payment required' })
}
// Build payment required
const paymentRequired = buildPaymentRequired(planId, {
endpoint: req.url,
agentId: agentId,
httpVerb: req.method,
})
try {
// Verify permissions
const verification = await agentPayments.facilitator.verifyPermissions({
paymentRequired,
x402AccessToken: accessToken,
maxAmount: 1n, // Max credits to verify
})
if (!verification.isValid) {
return res.status(402).json({ error: 'Insufficient credits or invalid token' })
}
// Verification successful, proceed with request...
} catch (error) {
return res.status(500).json({ error: error.message })
}
})
Settle Permissions
After successfully processing the request, burn the credits:
app.post('/api/v1/tasks', async (req, res) => {
const accessToken = req.headers['payment-signature'] as string
if (!accessToken) {
return res.status(402).json({ error: 'Payment required' })
}
const paymentRequired = buildPaymentRequired(planId, {
endpoint: req.url,
agentId: agentId,
httpVerb: req.method,
})
try {
// Verify
const verification = await agentPayments.facilitator.verifyPermissions({
paymentRequired,
x402AccessToken: accessToken,
maxAmount: 1n,
})
if (!verification.isValid) {
return res.status(402).json({ error: 'Insufficient credits' })
}
// Execute task
const result = await processTask(req.body)
// Settle permissions (burn credits)
const settlement = await agentPayments.facilitator.settlePermissions({
paymentRequired,
x402AccessToken: accessToken,
maxAmount: 1n, // Credits to burn
})
// Return success with payment metadata
return res.json({
result,
transaction: settlement.transaction,
creditsUsed: settlement.creditsRedeemed,
remainingBalance: settlement.remainingBalance,
})
} catch (error) {
return res.status(500).json({ error: error.message })
}
})
Return 402 Payment Required
When payment is required, return HTTP 402 with the PAYMENT-REQUIRED header:
import { buildPaymentRequired } from '@nevermined-io/payments'
function return402Response(res: express.Response, planId: string, agentId: string, endpoint: string) {
const paymentRequired = buildPaymentRequired(planId, {
endpoint,
agentId,
httpVerb: 'POST',
})
// Encode payment required as base64
const paymentRequiredHeader = Buffer.from(
JSON.stringify(paymentRequired)
).toString('base64')
res.writeHead(402, {
'Content-Type': 'application/json',
'PAYMENT-REQUIRED': paymentRequiredHeader,
})
res.end(JSON.stringify({
error: 'Payment required',
message: 'Valid X402 access token required in PAYMENT-SIGNATURE header',
}))
}
// Usage
app.post('/api/v1/tasks', async (req, res) => {
const accessToken = req.headers['payment-signature'] as string
if (!accessToken) {
return return402Response(res, planId, agentId, req.url)
}
// Verify and process...
})
Complete Validation Example
import express from 'express'
import { Payments, EnvironmentName } from '@nevermined-io/payments'
import { buildPaymentRequired } from '@nevermined-io/payments'
const app = express()
app.use(express.json())
const agentPayments = Payments.getInstance({
nvmApiKey: process.env.NVM_API_KEY!,
environment: 'sandbox' as EnvironmentName,
})
const PLAN_ID = process.env.NVM_PLAN_ID!
const AGENT_ID = process.env.NVM_AGENT_ID!
app.post('/api/v1/tasks', async (req, res) => {
// 1. Extract token
const accessToken = req.headers['payment-signature'] as string
if (!accessToken) {
return return402Response(res, PLAN_ID, AGENT_ID, req.url)
}
// 2. Build payment required
const paymentRequired = buildPaymentRequired(PLAN_ID, {
endpoint: req.url,
agentId: AGENT_ID,
httpVerb: req.method,
})
try {
// 3. Verify permissions
const verification = await agentPayments.facilitator.verifyPermissions({
paymentRequired,
x402AccessToken: accessToken,
maxAmount: 1n,
})
if (!verification.isValid) {
console.log('Verification failed: insufficient credits or invalid token')
return return402Response(res, PLAN_ID, AGENT_ID, req.url)
}
console.log('✓ Permissions verified')
// 4. Execute task
const result = await processTask(req.body)
// 5. Settle permissions
const settlement = await agentPayments.facilitator.settlePermissions({
paymentRequired,
x402AccessToken: accessToken,
maxAmount: 1n,
})
console.log(`✓ Credits settled: ${settlement.creditsRedeemed}`)
console.log(`✓ Transaction: ${settlement.transaction}`)
// 6. Return response with payment metadata
const paymentResponse = {
success: settlement.success,
network: settlement.network,
transaction: settlement.transaction,
creditsRedeemed: settlement.creditsRedeemed,
}
res.writeHead(200, {
'Content-Type': 'application/json',
'PAYMENT-RESPONSE': Buffer.from(JSON.stringify(paymentResponse)).toString('base64'),
})
res.end(JSON.stringify({ result }))
} catch (error) {
console.error('Request processing failed:', error)
res.status(500).json({ error: error.message })
}
})
function return402Response(
res: express.Response,
planId: string,
agentId: string,
endpoint: string
) {
const paymentRequired = buildPaymentRequired(planId, {
endpoint,
agentId,
httpVerb: 'POST',
})
const paymentRequiredHeader = Buffer.from(
JSON.stringify(paymentRequired)
).toString('base64')
res.writeHead(402, {
'Content-Type': 'application/json',
'PAYMENT-REQUIRED': paymentRequiredHeader,
})
res.end(JSON.stringify({ error: 'Payment required' }))
}
async function processTask(body: any): Promise<any> {
// Your agent logic here
return { response: 'Task completed' }
}
app.listen(3000, () => {
console.log('Agent server running on port 3000')
})
Dynamic Credit Burning
For agents with variable credit costs, calculate credits based on the request:
async function calculateCredits(request: any, response: any): Promise<bigint> {
// Example: charge based on response length
const responseLength = JSON.stringify(response).length
return BigInt(Math.ceil(responseLength / 1000)) // 1 credit per KB
}
app.post('/api/v1/tasks', async (req, res) => {
const accessToken = req.headers['payment-signature'] as string
const paymentRequired = buildPaymentRequired(PLAN_ID, {
endpoint: req.url,
agentId: AGENT_ID,
httpVerb: req.method,
})
// Verify with max possible credits
const verification = await agentPayments.facilitator.verifyPermissions({
paymentRequired,
x402AccessToken: accessToken,
maxAmount: 10n, // Max credits subscriber might need
})
if (!verification.isValid) {
return return402Response(res, PLAN_ID, AGENT_ID, req.url)
}
// Execute
const result = await processTask(req.body)
// Calculate actual credits used
const creditsUsed = await calculateCredits(req.body, result)
// Settle with actual amount
const settlement = await agentPayments.facilitator.settlePermissions({
paymentRequired,
x402AccessToken: accessToken,
maxAmount: creditsUsed,
})
return res.json({
result,
creditsUsed: settlement.creditsRedeemed,
})
})
Settle Options
The settlePermissions method accepts additional options:
const settlement = await agentPayments.facilitator.settlePermissions({
paymentRequired,
x402AccessToken: accessToken,
maxAmount: 5n, // Credits to burn
batch: false, // Batch settlement (optional)
marginPercent: 5, // Add 5% margin to calculated cost (optional)
agentRequestId: 'req-123', // Request ID from verification (optional)
})
Best Practices
- Always Verify First: Call
verifyPermissions before executing tasks
- Settle After Success: Only burn credits after successful task completion
- Handle Errors: Wrap verification/settlement in try-catch blocks
- Return 402 Properly: Include
PAYMENT-REQUIRED header with payment details
- Log Transactions: Record transaction hashes for audit trails
- Dynamic Pricing: Calculate credits based on actual resource usage
- Token Validation: Never skip verification even if token looks valid
Source References:
src/x402/facilitator-api.ts (buildPaymentRequired, verifyPermissions, settlePermissions)
tests/e2e/test_payments_e2e.test.ts (MockAgentServer class, complete validation flow)
tests/e2e/test_x402_e2e.test.ts (lines 135-150, verification examples)