Independent review. This site is not the official website and is not affiliated with, endorsed by, or operated by the wallet vendor reviewed here. Never enter your seed phrase or private keys on any third-party site.

x402 Node.js Express Example: Build a Payment Middleware

Get Free Crypto Wallets Network

x402 Node.js Express Example: Build a Payment Middleware


Introduction

If you’ve built crypto×AI agents or dApps, you know that integrating smooth, reliable payments can make or break your user experience. The x402 protocol provides a novel method for paywalls based on direct agent micropayments, especially leveraging stablecoins like USDC on-chain.

In my experience working on agent payment integrations, the Node.js ecosystem—with Express as the server workhorse—is a natural fit for building a payment middleware that handles x402 on-chain payment demands and HTTP 402 responses. This tutorial walks you through a hands-on example to build a simple paywall in Node.js with Express, handling micropayments via USDC and the x402 standard.

Whether you’re aiming to gate API access, on-chain AI compute, or MCP services, you’ll find practical guidance and runnable code here.

Check out the x402 protocol tutorial for an overview if you’re new.


Prerequisites and Setup

Before jumping in, here are the versions and tools I used:

  • Node.js v18+
  • Express 4.x
  • ethers.js 6.x (for Ethereum RPC and wallet interactions)
  • dotenv (to handle environment variables)
  • USDC contract ABI (ERC-20 standard)

Make sure you have access to an Ethereum-compatible testnet RPC (e.g., Goerli or Sepolia). Using a testnet avoids wasting real USDC and prevents costly errors.

Start a project and install dependencies:

mkdir x402-express-paywall && cd x402-express-paywall
npm init -y
npm install express ethers dotenv

Create a .env file to store your wallet private key and RPC:

RPC_URL=https://goerli.infura.io/v3/YOUR_INFURA_PROJECT_ID
PRIVATE_KEY=your-testnet-wallet-private-key
USDC_ADDRESS=0xYourTestnetUSDCContract

Never commit private keys or secrets to version control.


Understanding x402 and Payment Middleware

The x402 protocol extends the HTTP ecosystem with a microtransaction payment layer to enforce paywalls at the API or service level. When a client accesses a protected resource, the server returns an HTTP 402 Payment Required response along with payment instructions specifying an on-chain payment to unlock the resource.

Your middleware’s job is to:

  • Detect if the request lacks a valid payment
  • Return HTTP 402 with payment details (recipient, amount, token)
  • Verify incoming payments on-chain before allowing access

Think of it as a server-side gatekeeper that verifies your autonomous agent or user has paid the USDC micropayment required before serving the actual content.

This differs from legacy API keys or OAuth tokens — it’s real money moving on-chain, allowing dynamic pay-per-use access.


Creating the Express Paywall Middleware

Let’s build a basic Express middleware that checks a simple auth header to simulate payment validation logic. Later, you’ll replace this with real on-chain payment verification.

// paywall.js
const { ethers } = require('ethers');
require('dotenv').config();

const USDC_ADDRESS = process.env.USDC_ADDRESS;
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

// Minimal ERC20 ABI fragment
const ERC20_ABI = [
  'function balanceOf(address) view returns (uint256)',
  'function decimals() view returns (uint8)'
];

const usdcContract = new ethers.Contract(USDC_ADDRESS, ERC20_ABI, provider);

// Config: how much USDC is needed per request (in USDC units)
const USDC_PRICE = 0.01;

async function hasSufficientPayment(address) {
  const balance = await usdcContract.balanceOf(address);
  const decimals = await usdcContract.decimals();
  const normalizedBalance = Number(balance) / 10 ** decimals;
  return normalizedBalance >= USDC_PRICE;
}

// Middleware function
async function x402Paywall(req, res, next) {
  try {
    /**
     * For demo: expect 'x-wallet' header with wallet address
     * Production: better auth and payment receipts verification needed
     */
    const clientAddress = req.headers['x-wallet'];

    if (!clientAddress) {
      // Respond with HTTP 402 and payment instructions
      res.status(402).json({
        error: 'Payment required',
        payTo: wallet.address,
        token: 'USDC',
        amount: USDC_PRICE
      });
      return;
    }

    const paid = await hasSufficientPayment(clientAddress);

    if (!paid) {
      res.status(402).json({
        error: 'Insufficient funds',
        payTo: wallet.address,
        token: 'USDC',
        amount: USDC_PRICE
      });
      return;
    }

    next(); // Payment verified, allow access

  } catch (err) {
    console.error('Paywall error:', err);
    res.status(500).json({ error: 'Internal server error' });
  }
}

module.exports = x402Paywall;

This simple middleware expects an x-wallet HTTP header — the user’s agent wallet.

It checks if that wallet holds enough USDC balance, and if not, sends back a 402 with payment details.

Obviously, this naive model assumes off-chain balance verification and trust on the client header, so don’t apply it in production as-is.


Integrating USDC Micropayments API

Your production-grade x402 middleware will monitor on-chain payment receipts rather than just balances. You might watch event logs of USDC Transfer events or integrate an API that tracks micropayments.

Here’s an example snippet that listens for transfer approval events in ethers.js:

usdcContract.on('Transfer', (from, to, value, event) => {
  if (to.toLowerCase() === wallet.address.toLowerCase()) {
    console.log(`Received USDC payment: ${ethers.formatUnits(value, 6)} from ${from}`);
    // Here you’d update a payment status cache or DB
  }
});

You can combine this with caching payment status per client wallet address to efficiently gate requests without on-chain calls every time.

Another approach is an off-chain indexing service (like The Graph or a dedicated MCP server endpoint) that aggregates payments and exposes a fast API.

Remember — micropayment APIs are still bleeding-edge in the x402 world, expect changes.


Handling HTTP 402 Payment Required

A key part of the payment middleware is responding correctly to clients needing to pay.

Here’s an example HTTP 402 response body:

{
  "error": "Payment required",
  "payTo": "0xYourWalletAddress",
  "token": "USDC",
  "amount": 0.01
}

Clients or agent software should parse these and react by prompting users or initiating a transaction.

HTTP 402 isn’t commonly used on the mainstream web, but for x402 it becomes a semantic signal for paywalls.

I’ve found that making this response machine-readable enables seamless UX for autonomous agents.


Testing the Middleware Locally

Here’s a basic Express app wiring this middleware:

// app.js
const express = require('express');
const x402Paywall = require('./paywall');

const app = express();
const PORT = process.env.PORT || 3000;

// Protect the /protected endpoint
app.use('/protected', x402Paywall);

app.get('/protected', (req, res) => {
  res.send('Access granted to paid resource');
});

app.listen(PORT, () => {
  console.log(`Server listening on http://localhost:${PORT}`);
});

Run this locally with node app.js and test with curl:

  • Without wallet header:
curl -i http://localhost:3000/protected

You should see HTTP 402 with payment instructions.

  • With header (replace with your test wallet):
curl -i -H "x-wallet: 0xClientWalletAddress" http://localhost:3000/protected

If this wallet has enough USDC on testnet, the response will grant access.

You can simulate wallets or fund test wallets using faucet services for your testnet.


Security Considerations

Handling agent micropayments and wallets opens a few attack vectors:

  • Header spoofing: The x-wallet header is easily faked; don’t trust it fully without signed receipts or Post-Message proofs.

  • Unlimited approvals: USDC or ERC-20 token approvals may be set too high, risking token drains.

  • Mainnet vs testnet: Always test on testnets first. Be mindful of token decimals and chain-specific nuances.

  • Untrusted MCP servers: If relying on third-party MCP indexing servers for payments, ensure robust fallback and validation.

In production, integrating session keys with spending limits or gasless meta-transactions (account abstraction) can improve security and user UX.

Slither and Aderyn can help audit your smart contracts for unsafe payment-related patterns.


Common Gotchas and Troubleshooting

Q: Why isn’t my balance check returning expected values?

  • Check USDC decimals — it’s usually 6, not 18 like ETH.
  • Confirm you query the correct testnet contract address.
  • Refresh RPC or try multiple RPC providers to avoid rate limits.

Q: 402 error persists after payment?

  • The middleware may rely on balance snapshots rather than events.
  • Consider caching delays or delayed transaction finality.
  • Verify payment token and recipient match exactly.

Q: How do I scale this for many agents?

  • Off-chain payment aggregators or MCP indexing servers help.
  • Use session keys or signed proof-of-payment tokens to reduce on-chain calls.

For more troubleshooting tips, see the agent payments protocol comparisons and troubleshooting FAQ pages, (troubleshooting-faq).


Conclusion and Next Steps

Building a paywall in Node.js with x402 and USDC micropayments gives your agent-powered apps a flexible way to monetize autonomous services and APIs.

I encourage you to extend this example by adding:

  • Full on-chain payment event listening
  • Signed payment proof validation to prevent spoofing
  • Session keys with spending limits (ERC-4337 style)
  • Integration with MCP servers for scalable payment verification

And of course, explore other language SDKs like the x402 Python FastAPI setup for different stacks.

Payments are complex but manageable with the right tooling and patterns. Try this example, tweak it to your needs, and share your experiences in the developer community.

Happy building!

Get Free Crypto Wallets Network