Advanced Server Patterns
Lifecycle hooks, dynamic pricing, custom tokens, and Bazaar discovery
@x402/express Advanced Examples
Express.js server demonstrating advanced x402 patterns including dynamic pricing, payment routing, lifecycle hooks and API discoverability.
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
const resourceServer = new x402ResourceServer(new HTTPFacilitatorClient({ url: facilitatorUrl }))
.register("eip155:84532", new ExactEvmScheme())
.onBeforeVerify(async ctx => console.log("Verifying payment..."))
.onAfterSettle(async ctx => console.log("Settled:", ctx.result.transaction));
app.use(
paymentMiddleware(
{
"GET /weather": {
accepts: {
scheme: "exact",
price: ctx => (ctx.adapter.getQueryParam?.("tier") === "premium" ? "$0.01" : "$0.001"),
network: "eip155:84532",
payTo: evmAddress,
},
},
},
resourceServer,
),
);
Prerequisites
- Node.js v20+ (install via nvm)
- pnpm v10 (install via pnpm.io/installation)
- Valid EVM for receiving payments
- URL of a facilitator supporting the desired payment network, see facilitator list
Setup
- Copy
.env-localto.env:
cp .env-local .env
and fill required environment variables:
FACILITATOR_URL- Facilitator endpoint URLEVM_ADDRESS- Ethereum address to receive payments
- Install and build all packages from the typescript examples root:
cd ../../
pnpm install && pnpm build
cd servers/advanced
- Run the server
pnpm dev
Available Examples
Each example demonstrates a specific advanced pattern:
| Example | Command | Description |
|---|---|---|
bazaar | pnpm dev:bazaar | API discoverability via Bazaar |
hooks | pnpm dev:hooks | Payment lifecycle hooks |
dynamic-price | pnpm dev:dynamic-price | Context-based pricing |
dynamic-pay-to | pnpm dev:dynamic-pay-to | Route payments to different recipients |
custom-money-definition | pnpm dev:custom-money-definition | Accept alternative tokens |
Testing the Server
You can test the server using one of the example clients:
Using the Fetch Client
cd ../../clients/fetch
# Ensure .env is setup
pnpm dev
Using the Axios Client
cd ../../clients/axios
# Ensure .env is setup
pnpm dev
Example: Bazaar Discovery
Adding the discovery extension to make your API discoverable:
import { declareDiscoveryExtension } from "@x402/extensions/bazaar";
app.use(
paymentMiddleware(
{
"GET /weather": {
accepts: {
scheme: "exact",
price: "$0.001",
network: "eip155:84532",
payTo: evmAddress,
},
description: "Weather data",
mimeType: "application/json",
extensions: {
...declareDiscoveryExtension({
input: { city: "San Francisco" },
inputSchema: {
properties: { city: { type: "string" } },
required: ["city"],
},
output: {
example: { city: "San Francisco", weather: "foggy", temperature: 60 },
},
}),
},
},
},
resourceServer,
),
);
Use case: Clients and AI agents can easily discover your service
Example: Dynamic Pricing
Calculate prices at runtime based on request context:
app.use(
paymentMiddleware(
{
"GET /weather": {
accepts: {
scheme: "exact",
price: context => {
const tier = context.adapter.getQueryParam?.("tier") ?? "standard";
return tier === "premium" ? "$0.005" : "$0.001";
},
network: "eip155:84532",
payTo: evmAddress,
},
},
},
resourceServer,
),
);
Use case: Implementing tiered pricing, user-based pricing, content-based pricing or any scenario where the price varies based on the request.
Example: Dynamic PayTo
Route payments to different recipients based on request context:
const addressLookup: Record<string, `0x${string}`> = {
US: "0x...",
UK: "0x...",
};
app.use(
paymentMiddleware(
{
"GET /weather": {
accepts: {
scheme: "exact",
price: "$0.001",
network: "eip155:84532",
payTo: context => {
const country = context.adapter.getQueryParam?.("country") ?? "US";
return addressLookup[country];
},
},
},
},
resourceServer,
),
);
Use case: Marketplace applications where payments should go to different sellers, content creators, or service providers based on the resource being accessed.
Example: Lifecycle Hooks
Run custom logic before/after verification and settlement:
const resourceServer = new x402ResourceServer(facilitatorClient)
.register("eip155:84532", new ExactEvmScheme())
.onBeforeVerify(async context => {
console.log("Before verify hook", context);
// Abort verification by returning { abort: true, reason: string }
})
.onAfterSettle(async context => {
await logPaymentToDatabase(context);
})
.onSettleFailure(async context => {
// Return a result with recovered=true to recover from the failure
// return { recovered: true, result: { success: true, transaction: "0x123..." } };
});
Available hooks:
onBeforeVerify— Run before verification (can abort)onAfterVerify— Run after successful verificationonVerifyFailure— Run when verification fails (can recover)onBeforeSettle— Run before settlement (can abort)onAfterSettle— Run after successful settlementonSettleFailure— Run when settlement fails (can recover)
Use case:
- Log payment events to a database or monitoring system
- Perform custom validation before processing payments
- Implement retry or recovery logic for failed payments
- Trigger side effects (notifications, database updates) after successful payments
Example: Custom Tokens
Accept payments in custom tokens. Register a money parser on the scheme to support alternative tokens for specific networks.
import { ExactEvmScheme } from "@x402/evm/exact/server";
const resourceServer = new x402ResourceServer(facilitatorClient).register(
"eip155:84532",
new ExactEvmScheme().registerMoneyParser(async (amount, network) => {
// Use Wrapped XDAI on Gnosis Chain
if (network === "eip155:100") {
return {
amount: BigInt(Math.round(amount * 1e18)).toString(),
asset: "0xe91d153e0b41518a2ce8dd3d7944fa863463a97d",
extra: { token: "Wrapped XDAI" },
};
}
return null; // Fall through to default parser
}),
);
// Use in payment requirements
"GET /weather": {
accepts: {
scheme: "exact",
price: "$0.001",
network: "eip155:100",
payTo: evmAddress,
},
},
Use case: When you want to accept payments in tokens other than USDC, or use different tokens based on conditions (e.g., DAI for large amounts, custom tokens for specific networks).
Response Format
Payment Required (402)
HTTP/1.1 402 Payment Required
Content-Type: application/json; charset=utf-8
PAYMENT-REQUIRED: <base64-encoded JSON>
{}
The PAYMENT-REQUIRED header contains base64-encoded JSON with the payment requirements. Note: amount is in atomic units (e.g., 1000 = 0.001 USDC, since USDC has 6 decimals).
{
"x402Version": 2,
"error": "Payment required",
"resource": {
"url": "http://localhost:4021/weather",
"description": "Weather data",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:84532",
"amount": "1000",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"payTo": "0x...",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USDC",
"version": "2",
"resourceUrl": "http://localhost:4021/weather"
}
}
]
}
Successful Response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
PAYMENT-RESPONSE: <base64-encoded JSON>
{"report":{"weather":"sunny","temperature":70}}
The PAYMENT-RESPONSE header contains base64-encoded JSON with the settlement details:
{
"success": true,
"transaction": "0x...",
"network": "eip155:84532",
"payer": "0x...",
"requirements": {
"scheme": "exact",
"network": "eip155:84532",
"amount": "1000",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"payTo": "0x...",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USDC",
"version": "2",
"resourceUrl": "http://localhost:4021/weather"
}
}
}
Related Content
Looking for more? Check out our other typescript examples or browse by server content.