Skip to main content

Express.js Integration

Start accepting x402 payments in your Express server in 2 minutes

Share
Last updated on October 5, 2026
On this page

Start accepting x402 payments in your Express server in 2 minutes.

Example Code

You can find the full code for this example on GitHub.

Step 1: Install Dependencies​

Install the required packages for your Express server:

bash
npm install x402 express dotenv

Or with other package managers:

bash
# pnpm
pnpm add x402 express dotenv
 
# yarn
yarn add x402 express dotenv
 
# bun
bun add x402 express dotenv

Step 2: Set Your Environment Variables​

Open your generated project's .env and set:

  • FACILITATOR_URL: Facilitator base URL (defaults to: https://x402.bex.co)
  • NETWORK: Network to use for the facilitator (default: sui)
  • ADDRESS: Wallet public address to receive payments to
  • BEX_API_KEY: Your bex.co API key from dashboard
env
FACILITATOR_URL=https://x402.bex.co
NETWORK=sui
ADDRESS=0x... # wallet public address you want to receive payments to
BEX_API_KEY=your_api_key_here
Network Options

bex.co supports multiple networks:

  • sui (recommended for fastest settlement)
  • ethereum
  • base
  • polygon
  • avalanche

Step 3: Create Your Express Server​

Create an index.ts file with the following code:

typescript
import { config } from "dotenv";
import express from "express";
import { paymentMiddleware, Network, Resource } from "x402-express";
 
config();
 
const facilitatorUrl = process.env.FACILITATOR_URL as Resource;
const payTo = process.env.ADDRESS as `0x${string}`;
const network = process.env.NETWORK as Network;
const apiKey = process.env.BEX_API_KEY;
 
if (!facilitatorUrl || !payTo || !apiKey || !network) {
  console.error("Missing required environment variables");
  process.exit(1);
}
 
const app = express();
 
app.use(
  paymentMiddleware(
    payTo,
    {
      "GET /weather": {
        // USDC amount in dollars
        price: "$0.001",
        network,
      },
      "/premium/*": {
        // Define atomic amounts in any supported token
        price: {
          amount: "100000",
          asset: {
            address: "0xabc",
            decimals: 18,
            name: "USDC",
          },
        },
        network,
      },
    },
    {
      url: facilitatorUrl,
      apiKey: apiKey,
    },
  ),
);
 
app.get("/weather", (req, res) => {
  res.send({
    report: {
      weather: "sunny",
      temperature: 70,
    },
  });
});
 
app.get("/premium/content", (req, res) => {
  res.send({
    content: "This is premium content",
  });
});
 
app.listen(4021, () => {
  console.log(`Server listening at http://localhost:4021`);
});

Step 4: Run the Server​

bash
npx tsx index.ts

Or add a script to your package.json:

json
{
  "scripts": {
    "dev": "tsx watch index.ts",
    "start": "node dist/index.js"
  }
}

Then run:

bash
npm run dev
Your server is now accepting 402 payments!

Step 5: Test the Server​

You can test payments against your server locally using HTTP clients like curl, Postman, or by building a client application.

Coming Soon

Client implementation guides for fetch API and axios will be available soon.

Payment Configuration Options​

The paymentMiddleware accepts flexible payment configurations:

Simple Dollar Amount​

typescript
"GET /weather": {
  price: "$0.001",
  network: "sui",
}

Atomic Token Amount​

typescript
"/premium/*": {
  price: {
    amount: "100000",
    asset: {
      address: "0x...",
      decimals: 6,
      name: "USDC",
    },
  },
  network: "sui",
}

Route Patterns​

You can use wildcards and specific HTTP methods:

typescript
{
  "GET /weather": { /* ... */ },      // Exact GET route
  "/premium/*": { /* ... */ },        // Any method, wildcard path
  "POST /api/analyze": { /* ... */ }, // Exact POST route
}

Advanced Features​

Custom Payment Verification​

typescript
app.use(
  paymentMiddleware(payTo, paymentConfig, {
    url: facilitatorUrl,
    apiKey: apiKey,
    onPaymentVerified: (req, paymentData) => {
      console.log("Payment verified:", paymentData.txHash);
      // Custom logging or analytics
    },
  }),
);

Error Handling​

typescript
app.use((err, req, res, next) => {
  if (err.name === "X402PaymentError") {
    res.status(402).json({
      error: "Payment required",
      details: err.message,
    });
  } else {
    next(err);
  }
});

Production Deployment​

Before deploying to production:

  1. Switch to production facilitator endpoint
  2. Use production network (e.g., sui instead of sui-testnet)
  3. Enable HTTPS for your server
  4. Set up proper error logging
  5. Configure CORS if serving cross-origin clients
typescript
// Production configuration
const app = express();
 
// Enable CORS
app.use(
  cors({
    origin: process.env.ALLOWED_ORIGINS?.split(","),
    credentials: true,
  }),
);
 
// Use production network
app.use(
  paymentMiddleware(
    payTo,
    {
      "GET /weather": {
        price: "$0.01",
        network: "sui", // Production Sui network
      },
    },
    {
      url: "https://x402.bex.co",
      apiKey: process.env.BEX_API_KEY,
    },
  ),
);

Troubleshooting​

Common Issues​

Payment verification fails

  • Verify your BEX_API_KEY is correct
  • Check that the wallet address format matches the network
  • Ensure the facilitator URL is accessible

CORS errors

  • Add CORS middleware before payment middleware
  • Configure allowed origins properly

Use the CORS debugger with the page origin, endpoint, method, credentials mode, and headers your client code sets. In the browser's Network panel, copy the OPTIONS response status and headers separately from the payment request's final response. Leave missing responses blank; do not combine a redirect chain into one header block. Omit browser-managed headers such as Cookie and Origin from the request-header input.

With credentials: "include", return an explicitly allowed origin and Access-Control-Allow-Credentials: true on both responses. Validate the origin against your application's allowlist before reflecting it. Authorization must be named in Access-Control-Allow-Headers; a wildcard does not cover it. Use the payment-header names shown by your installed SDK's actual request. If JavaScript must read a custom payment-response header, list it in Access-Control-Expose-Headers as well. Allowing the request does not expose every response header.

The tool checks pasted evidence locally. Its generated curl commands inspect HTTP responses; curl does not enforce browser CORS or reproduce browser cookie policy. See the Fetch Standard's CORS protocol for the underlying rules.

Network mismatch

  • Ensure client and server use the same network
  • Check that the token address is valid for the network

Next Steps​

Need Help?​

Join Our Community

Have questions or want to connect with other developers?

Join Discord

Was this page helpful?

One API key for 30+ chains

bex router is a single multi-chain gateway — one key, one bill, metered in compute units instead of an account per network.

Explore bex router