Agentic Payments & Settlement

Add Payments to Your Express.js AI Agent in One Line of Code

By
Rod
February 7, 2026

A developer's guide to monetizing AI agents with the Nevermined x402 Express middleware

If you've built an AI agent with Express.js, you've probably struggled with the payment problem. How do you:

  • Charge users per request?
  • Verify they have credits before executing expensive AI calls?
  • Track usage and settle payments?
  • Handle all the edge cases (expired tokens, insufficient balance, failed settlements)?

The traditional approach involves dozens of lines of boilerplate: checking auth headers, calling payment APIs, wrapping everything in try/catch blocks, and hoping you didn't miss an edge case.

What if you could do it in one line?

Introducing paymentMiddleware

The @nevermined-io/payments package now includes an Express middleware that handles the entire x402 payment flow:

import { paymentMiddleware } from "@nevermined-io/payments/express";


app.use(
 paymentMiddleware(payments, {
   "POST /ask": { planId: process.env.NVM_PLAN_ID!, credits: 1 },
 })
);

That's it. Your /ask endpoint is now payment-protected.

What the Middleware Does

When a request comes in without a payment token:

POST /ask HTTP/1.1
Content-Type: application/json


{"query": "What is the meaning of life?"}

The middleware returns a 402 Payment Required with the payment-required header:

HTTP/1.1 402 Payment Required
payment-required: eyJ4NDAyVmVyc2lvbiI6MiwuLi59


{"error": "Payment Required"}

When the client retries with an x402 token in the payment-signature header:

POST /ask HTTP/1.1
Content-Type: application/json
payment-signature: eyJ4NDAyVmVyc2lvbiI6MiwuLi59


{"query": "What is the meaning of life?"}

The middleware:

  • Verifies the token with the Nevermined Facilitator
  • Calls your route handler
  • Settles the payment (burns credits)
  • Returns a settlement receipt in the payment-response header

Your route handler never sees the payment logic—it just processes the request.

Complete Example

Here's a fully working AI agent with payment protection:

// src/agent.ts
import express from "express";
import OpenAI from "openai";
import { Payments } from "@nevermined-io/payments";
import { paymentMiddleware } from "@nevermined-io/payments/express";


const app = express();
app.use(express.json());


// Initialize services
const payments = Payments.getInstance({
 nvmApiKey: process.env.NVM_API_KEY!,
 environment: "sandbox",
});


const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });


// Add payment protection
app.use(
 paymentMiddleware(payments, {
   "POST /ask": { planId: process.env.NVM_PLAN_ID!, credits: 1 },
 })
);


// Your route handler - no payment logic needed!
app.post("/ask", async (req, res) => {
 const { query } = req.body;


 const completion = await openai.chat.completions.create({
   model: "gpt-4o-mini",
   messages: [{ role: "user", content: query }],
 });


 res.json({ response: completion.choices[0]?.message?.content });
});


// Health check (not payment-protected)
app.get("/health", (req, res) => res.json({ status: "ok" }));


app.listen(3000, () => console.log("Agent running on http://localhost:3000"));

Route Configuration Options

Fixed Credits

Charge the same amount for every request:

paymentMiddleware(payments, {
 "POST /ask": { planId: PLAN_ID, credits: 1 },
 "POST /generate-image": { planId: PLAN_ID, credits: 10 },
});

Dynamic Credits

Calculate credits based on the request or response:

paymentMiddleware(payments, {
 "POST /generate": {
   planId: PLAN_ID,
   credits: (req, res) => {
     // Charge based on token count in response
     const tokens = res.locals.tokenCount || 100;
     return Math.ceil(tokens / 100);
   },
 },
});

Path Parameters

Works with Express route patterns:

paymentMiddleware(payments, {
 "GET /users/:id": { planId: PLAN_ID, credits: 1 },
 "POST /agents/:agentId/task": { planId: PLAN_ID, credits: 5 },
});

Middleware Options

Customize behavior with options:

paymentMiddleware(payments, routes, {
 // Custom token header(s)
 tokenHeader: "payment-signature",


 // Hook before verification
 onBeforeVerify: (req, paymentRequired) => {
   console.log(`[Payment] Verifying request to ${req.path}`);
 },


 // Hook after settlement
 onAfterSettle: (req, creditsUsed, settlement) => {
   console.log(
     `[Payment] Settled ${creditsUsed} credits, tx: ${settlement.txHash}`
   );
 },


 // Custom error handler
 onPaymentError: (error, req, res) => {
   console.error(`[Payment] Error: ${error.message}`);
   res.status(402).json({ error: error.message });
 },
});

Client Implementation

Here's how clients interact with your payment-protected API:

// src/client.ts
import { Payments } from "@nevermined-io/payments";
import { X402_HEADERS } from "@nevermined-io/payments/express";


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


async function callProtectedAPI() {
 const SERVER_URL = "http://localhost:3000";


 // Step 1: Request without token → 402
 const response1 = await fetch(`${SERVER_URL}/ask`, {
   method: "POST",
   headers: { "Content-Type": "application/json" },
   body: JSON.stringify({ query: "What is 2+2?" }),
 });


 if (response1.status === 402) {
   // Step 2: Decode payment requirements
   const paymentRequired = JSON.parse(
     Buffer.from(
       response1.headers.get(X402_HEADERS.PAYMENT_REQUIRED)!,
       "base64"
     ).toString()
   );


   const { planId, extra } = paymentRequired.accepts[0];
   const agentId = extra?.agentId;


   // Step 3: Generate x402 token
   const { accessToken } = await payments.x402.getX402AccessToken(
     planId,
     agentId
   );


   // Step 4: Request with token → 200
   const response2 = await fetch(`${SERVER_URL}/ask`, {
     method: "POST",
     headers: {
       "Content-Type": "application/json",
       [X402_HEADERS.PAYMENT_SIGNATURE]: accessToken,
     },
     body: JSON.stringify({ query: "What is 2+2?" }),
   });


   const data = await response2.json();
   console.log("Response:", data.response);


   // Step 5: Decode settlement receipt
   const settlement = JSON.parse(
     Buffer.from(
       response2.headers.get(X402_HEADERS.PAYMENT_RESPONSE)!,
       "base64"
     ).toString()
   );
   console.log("Credits used:", settlement.creditsRedeemed);
 }
}


callProtectedAPI();

x402 Headers Reference

Header Direction Description
payment-signature
Client → Server
Base64-encoded x402 access token
payment-required
Server → Client (402)
Base64-encoded payment requirements
payment-response
Server → Client (200)
Base64-encoded settlement receipt

Environment Setup

# .env
NVM_API_KEY=nvm:your-api-key
NVM_ENVIRONMENT=sandbox
NVM_PLAN_ID=your-plan-id
OPENAI_API_KEY=sk-your-openai-api-key
PORT=3000

Installation

npm install @nevermined-io/payments express

For TypeScript:

npm install -D typescript @types/express @types/node tsx

Running the Example

# Terminal 1: Start the agent
npx tsx src/agent.ts


# Terminal 2: Run the client
npx tsx src/client.ts

Under the Hood

The middleware implements the x402 HTTP transport spec:

  • Request matching: Uses path-to-regexp to match routes
  • Token extraction: Reads from payment-signature or x-payment header
  • Verification: Calls payments.facilitator.verifyPermissions()
  • Route execution: Calls next() to let your handler run
  • Settlement: Calls payments.facilitator.settlePermissions()
  • Response headers: Sets payment-response with settlement receipt

The middleware wraps res.json() and res.send() to ensure settlement happens after your response is ready but before it's sent to the client.

Error Handling

Error HTTP Status Cause
Missing payment header
402
No payment-signature header
Invalid signature
402
Token signature verification failed
Insufficient balance
402
User needs to purchase more credits
Expired session
402
Session key has expired
Settlement failed
402
On-chain transaction failed

Advanced: Manual Integration

If you need more control than the middleware provides, you can implement the flow manually:

import { Payments } from "@nevermined-io/payments";
import { X402_HEADERS } from "@nevermined-io/payments/express";


app.post("/ask", async (req, res) => {
 const x402Token = req.headers["payment-signature"] as string;


 if (!x402Token) {
   // Return 402 with payment requirements
   const paymentRequired = {
     x402Version: 2,
     error: "Payment required",
     accepts: [
       {
         scheme: "nvm:erc4337",
         network: "eip155:84532",
         planId: process.env.NVM_PLAN_ID,
         extra: { version: "1" },
       },
     ],
     extensions: {},
   };
   res.set(
     X402_HEADERS.PAYMENT_REQUIRED,
     Buffer.from(JSON.stringify(paymentRequired)).toString("base64")
   );
   return res.status(402).json({ error: "Payment Required" });
 }


 // Verify
 const verification = await payments.facilitator.verifyPermissions({
   x402AccessToken: x402Token,
   maxAmount: BigInt(1),
 });


 if (!verification.success) {
   return res.status(402).json({ error: "Verification failed" });
 }


 // Execute workload
 const result = await generateAIResponse(req.body);


 // Settle
 const settlement = await payments.facilitator.settlePermissions({
   x402AccessToken: x402Token,
   maxAmount: BigInt(1),
 });


 // Return response with settlement receipt
 res.set(
   X402_HEADERS.PAYMENT_RESPONSE,
   Buffer.from(
     JSON.stringify({
       success: true,
       creditsRedeemed: 1,
       transactionHash: settlement.txHash,
     })
   ).toString("base64")
 );


 res.json({ result });
});

Resources

Complete Tutorial: http-simple-agent on GitHub
Express Integration Guide: Nevermined Docs
x402 Protocol Spec: x402 on GitHub
Nevermined x402 Spec: x402 Smart Accounts

Questions? Join us on Discord or open an issue on GitHub.

See Nevermined

in Action

Real-time payments, flexible pricing, and outcome-based monetization—all in one platform.

Schedule a demo
Rod

Senior Backend Engineer

Related Posts