Documentation Index Fetch the complete documentation index at: https://docs.semanticpay.io/llms.txt
Use this file to discover all available pages before exploring further.
This guide shows how to accept x402 payments for your API endpoints using Semantic’s facilitator on Plasma or Stable. By the end you’ll have an Express server that gates routes behind USD₮ payments.
Install
npm install @x402/express @x402/evm @x402/core express dotenv
How it works
Your server doesn’t handle payments directly. It delegates to Semantic’s facilitator:
A buyer hits your endpoint without a payment header
Your middleware responds with 402 Payment Required and the payment terms
The buyer’s x402 client signs an EIP-3009 authorization and retries
Your middleware forwards the signed payload to Semantic’s facilitator
The facilitator verifies the signature, settles on-chain, and confirms
Your route handler runs and returns the resource
You never touch private keys, gas tokens, or on-chain transactions. You just specify the price and the address to receive funds.
Pricing
The price field is a structured object that tells the buyer exactly what token to pay, how much, and on which chain. USDT0 uses 6 decimals, so "1000" = $0.001.
price : {
amount : "1000" , // base units (6 decimals)
asset : "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb" , // USDT0 contract
extra : { name : "USDT0" , version : "1" , decimals : 6 }, // EIP-712 domain info
}
The extra fields are passed through to the buyer’s client for EIP-712 signature construction. name and version must match what the on-chain USDT0 contract expects.
USDT0 Deployments
Plasma eip155:9745 · 0xB8C...5ebb
Stable eip155:988 · 0x779...3736
Full deployment list at docs.usdt0.to .
Minimal server (single chain)
import { config } from "dotenv" ;
import express from "express" ;
import { paymentMiddleware , x402ResourceServer } from "@x402/express" ;
import { ExactEvmScheme } from "@x402/evm/exact/server" ;
import { HTTPFacilitatorClient } from "@x402/core/server" ;
config ();
// --- Config ---
const PAY_TO = process . env . PAY_TO_ADDRESS as `0x ${ string } ` ;
const FACILITATOR_URL = "https://x402.semanticpay.io/" ;
const PLASMA_NETWORK = "eip155:9745" ;
const USDT0_PLASMA = "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb" ;
const PRICE = "1000" ; // $0.001 in base units
// --- Facilitator client ---
const facilitatorClient = new HTTPFacilitatorClient ({ url: FACILITATOR_URL });
// --- Express app ---
const app = express ();
app . use (
paymentMiddleware (
{
"GET /weather" : {
accepts: [
{
scheme: "exact" ,
network: PLASMA_NETWORK ,
price: {
amount: PRICE ,
asset: USDT0_PLASMA ,
extra: { name: "USDT0" , version: "1" , decimals: 6 },
},
payTo: PAY_TO ,
},
],
description: "Weather data" ,
mimeType: "application/json" ,
},
},
new x402ResourceServer ( facilitatorClient ). register (
PLASMA_NETWORK ,
new ExactEvmScheme (),
),
),
);
app . get ( "/weather" , ( req , res ) => {
res . json ({ weather: "sunny" , temperature: 70 });
});
app . get ( "/health" , ( req , res ) => {
res . json ({ status: "ok" , chain: "plasma" , payTo: PAY_TO });
});
const PORT = process . env . PORT || 4021 ;
app . listen ( PORT , () => {
console . log ( `Server listening at http://localhost: ${ PORT } ` );
console . log ( `Network: ${ PLASMA_NETWORK } ` );
console . log ( `USDT0: ${ USDT0_PLASMA } ` );
console . log ( `Pay to: ${ PAY_TO } ` );
});
Multi-chain server (Plasma + Stable)
Accept payments on both chains. The buyer’s client picks whichever network it has funds on.
import { config } from "dotenv" ;
import express from "express" ;
import { paymentMiddleware , x402ResourceServer } from "@x402/express" ;
import { ExactEvmScheme } from "@x402/evm/exact/server" ;
import { HTTPFacilitatorClient } from "@x402/core/server" ;
config ();
const PAY_TO = process . env . PAY_TO_ADDRESS as `0x ${ string } ` ;
const FACILITATOR_URL = "https://x402.semanticpay.io/" ;
// --- Network config ---
const NETWORKS = {
plasma: {
network: "eip155:9745" as const ,
usdt0: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb" ,
},
stable: {
network: "eip155:988" as const ,
usdt0: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736" ,
},
};
const PRICE = "1000" ; // $0.001
function priceOnChain ( chain : keyof typeof NETWORKS ) {
return {
amount: PRICE ,
asset: NETWORKS [ chain ]. usdt0 ,
extra: { name: "USDT0" , version: "1" , decimals: 6 },
};
}
// --- Facilitator + resource server ---
const facilitatorClient = new HTTPFacilitatorClient ({ url: FACILITATOR_URL });
const resourceServer = new x402ResourceServer ( facilitatorClient )
. register ( NETWORKS . plasma . network , new ExactEvmScheme ())
. register ( NETWORKS . stable . network , new ExactEvmScheme ());
// --- Express app ---
const app = express ();
app . use (
paymentMiddleware (
{
"GET /weather" : {
accepts: [
{
scheme: "exact" ,
network: NETWORKS . plasma . network ,
price: priceOnChain ( "plasma" ),
payTo: PAY_TO ,
},
{
scheme: "exact" ,
network: NETWORKS . stable . network ,
price: priceOnChain ( "stable" ),
payTo: PAY_TO ,
},
],
description: "Weather data" ,
mimeType: "application/json" ,
},
},
resourceServer ,
),
);
app . get ( "/weather" , ( req , res ) => {
res . json ({ weather: "sunny" , temperature: 70 });
});
app . get ( "/health" , ( req , res ) => {
res . json ({ status: "ok" });
});
const port = process . env . PORT || 4021 ;
app . listen ( port , () => {
console . log ( `Server listening at http://localhost: ${ port } ` );
});
Route configuration
The first argument to paymentMiddleware maps routes to payment requirements. The key format is METHOD /path.
paymentMiddleware (
{
"GET /api/data" : {
accepts: [
{
scheme: "exact" ,
network: "eip155:9745" ,
price: {
amount: "10000" , // $0.01
asset: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb" ,
extra: { name: "USDT0" , version: "1" , decimals: 6 },
},
payTo: PAY_TO ,
},
],
description: "Premium data feed" ,
mimeType: "application/json" ,
},
"POST /api/generate" : {
accepts: [
{
scheme: "exact" ,
network: "eip155:9745" ,
price: {
amount: "50000" , // $0.05
asset: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb" ,
extra: { name: "USDT0" , version: "1" , decimals: 6 },
},
payTo: PAY_TO ,
},
],
description: "AI generation endpoint" ,
mimeType: "application/json" ,
},
},
resourceServer ,
);
Routes not listed in the config are not gated — they behave like normal Express routes. This is why /health works without payment in the examples above.
Lifecycle events
The Semantic facilitator supports an optional X-Event-Callback header on /verify and /settle requests. When provided, the facilitator POSTs real-time lifecycle events to that URL as verification and settlement happen. This is useful for building dashboards, logging pipelines, or payment flow visualizations.
Events are fire-and-forget and do not block the facilitator’s response. If the callback URL is unreachable, events are silently dropped. If no header is provided, no events are sent.
Event types
Type When Key fields verify_startedFacilitator begins verifying the payment details.network, details.checksverify_completedVerification finished details.isValidverify_failedVerification threw an error details.errorsettle_startedFacilitator is broadcasting the on-chain transaction details.networksettle_completedTransaction confirmed on-chain details.transactionHashsettle_failedSettlement threw an error details.error
Example: receiving events
Add a POST endpoint to your server:
app . post ( "/payment-events" , ( req , res ) => {
const { type , title , details } = req . body ;
console . log ( `[ ${ type } ] ${ title } ` , details );
res . json ({ ok: true });
});
Then configure the facilitator client to include the callback header. Since HTTPFacilitatorClient from @x402/core doesn’t support custom headers directly, wrap fetch:
const CALLBACK_URL = "http://localhost:4021/payment-events" ;
const facilitatorClient = new HTTPFacilitatorClient ({
url: FACILITATOR_URL ,
fetch : ( url , init ) =>
fetch ( url , {
... init ,
headers: {
... init ?. headers ,
"X-Event-Callback" : CALLBACK_URL ,
},
}),
});
With this in place, every /verify and /settle call to the facilitator will include the callback header, and your /payment-events endpoint will receive events like:
{
"type" : "settle_completed" ,
"step" : 10 ,
"title" : "Settlement Confirmed" ,
"description" : "Payment transaction confirmed on blockchain" ,
"details" : {
"success" : true ,
"transactionHash" : "0xabc123..." ,
"network" : "eip155:9745"
},
"actor" : "blockchain" ,
"target" : "facilitator"
}
For the full event reference, see the Facilitator API docs .
Environment variables
# .env
PAY_TO_ADDRESS = 0xYourReceivingAddress
PORT = 4021
Using with other frameworks
x402 also provides middleware for Hono and Next.js:
# Hono
npm install @x402/hono
# Next.js
npm install @x402/next
The pattern is the same: create a facilitator client, register the EVM scheme for your network(s), and apply middleware. See the x402 examples for framework-specific code.