Start accepting x402 payments in your Express server in 2 minutes.
You can find the full code for this example on GitHub.
Step 1: Install Dependencies
Install the required packages for your Express server:
npm install x402 express dotenvOr with other package managers:
# pnpm
pnpm add x402 express dotenv
# yarn
yarn add x402 express dotenv
# bun
bun add x402 express dotenvStep 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 toBEX_API_KEY: Your bex.co API key from dashboard
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_herebex.co supports multiple networks:
sui(recommended for fastest settlement)ethereumbasepolygonavalanche
Step 3: Create Your Express Server
Create an index.ts file with the following code:
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
npx tsx index.tsOr add a script to your package.json:
{
"scripts": {
"dev": "tsx watch index.ts",
"start": "node dist/index.js"
}
}Then run:
npm run devStep 5: Test the Server
You can test payments against your server locally using HTTP clients like curl, Postman, or by building a client application.
Client implementation guides for fetch API and axios will be available soon.
Payment Configuration Options
The paymentMiddleware accepts flexible payment configurations:
Simple Dollar Amount
"GET /weather": {
price: "$0.001",
network: "sui",
}Atomic Token Amount
"/premium/*": {
price: {
amount: "100000",
asset: {
address: "0x...",
decimals: 6,
name: "USDC",
},
},
network: "sui",
}Route Patterns
You can use wildcards and specific HTTP methods:
{
"GET /weather": { /* ... */ }, // Exact GET route
"/premium/*": { /* ... */ }, // Any method, wildcard path
"POST /api/analyze": { /* ... */ }, // Exact POST route
}Advanced Features
Custom Payment Verification
app.use(
paymentMiddleware(payTo, paymentConfig, {
url: facilitatorUrl,
apiKey: apiKey,
onPaymentVerified: (req, paymentData) => {
console.log("Payment verified:", paymentData.txHash);
// Custom logging or analytics
},
}),
);Error Handling
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:
- Switch to production facilitator endpoint
- Use production network (e.g.,
suiinstead ofsui-testnet) - Enable HTTPS for your server
- Set up proper error logging
- Configure CORS if serving cross-origin clients
// 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_KEYis 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?