x402
Accept and make instant USD₮ payments over HTTP using WDK self-custodial wallets
What Is x402?
x402 is an open payment protocol, originally developed by Coinbase, that gives the long-reserved HTTP 402 Payment Required status code a concrete, blockchain-native meaning: if you want this resource, pay for it. No accounts, API keys, or checkout flows. Just plain HTTP.
This matters for AI agents because they need to pay for resources programmatically. x402 makes payment a first-class part of the web stack, so an agent can discover a price, sign a payment, and receive a resource in a single request-response cycle.
The Three Roles
| Role | Description |
|---|---|
| Client (Buyer) | The entity requesting a paid resource. Can be a human application, an AI agent, or any service with a wallet. |
| Resource Server (Seller) | The API or service providing the paid resource. Defines payment requirements and returns 402 for unpaid requests. |
| Facilitator | An intermediary that verifies payment signatures and submits transactions on-chain. Never holds funds, only executes signed authorizations. |
How the Protocol Works
Client requests a resource
A standard HTTP request. GET, POST, whatever your API expects.
Server responds with 402 Payment Required
The response body describes what to pay: amount, token, network, and recipient address.
{
"x402Version": 1,
"accepts": [{
"scheme": "exact",
"network": "eip155:9745",
"maxAmountRequired": "1000000",
"asset": "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb",
"resource": "https://api.example.com/data",
"payTo": "0x1234...abcd"
}]
}Client signs a payment
The client constructs an EIP-3009 transferWithAuthorization and signs it with their wallet. No tokens leave the wallet yet. It's a signed intent, not a transfer.
Client retries with payment header
The signed payload goes in the X-PAYMENT header on the same request.
Facilitator verifies
The server forwards the payload to the facilitator's /verify endpoint. The facilitator checks that the signature is valid, the amount is sufficient, and the payer has funds. No money moves yet.
Server performs the work
Inference, database query, generation, whatever the resource requires. This only happens after verification succeeds.
Facilitator settles on-chain
The server calls the facilitator's /settle endpoint. The facilitator submits the signed authorization on-chain, transferring tokens from buyer to seller.
Server returns the resource
200 OK with the result in the body and a settlement receipt in the X-PAYMENT-RESPONSE header.
For the full protocol specification, see x402.org and the x402 GitHub repository.
How to Use x402 With WDK
WDK wallets work as drop-in signers for x402. WalletAccountEvm satisfies the client x402 signer interface directly. Self-custodial x402 payments on any EVM chain.
This guide walks through three things:
- Client (Buyer) - Pay for x402-protected resources using a WDK wallet
- Server with Hosted Facilitator - Accept x402 payments by delegating verification and settlement to a third-party facilitator
- Server with Self-Hosted Facilitator - Run verification and settlement in-process using a WDK wallet, with no external dependencies
The x402 integration described on this page uses community-developed modules and third-party facilitator services. Tether does not endorse, operate, or assume legal or financial responsibility for any third-party facilitator. You are solely responsible for using any service.
Artificial intelligence and blockchain transactions carry inherent risks and limitations.
Recommended Chains
x402 with WDK works on any EVM chain where USD₮0 is deployed (see full list at docs.usdt0.to). However, we recommend Plasma and Stable for x402 payments. Both chains are purpose-built for USD₮ transfers with near-instant finality and near-zero fees. Agents only need to hold USD₮.
| Chain | CAIP-2 | RPC | USD₮0 Contract | Explorer |
|---|---|---|---|---|
| Plasma | eip155:9745 | https://rpc.plasma.to | 0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb | plasmascan.to |
| Stable | eip155:988 | https://rpc.stable.xyz | 0x779Ded0c9e1022225f8E0630b35a9b54bE713736 | stablescan.xyz |
Client: Paying for Resources
See the full working client example at x402/client.js.
npm install @tetherto/wdk-wallet-evm @x402/fetch @x402/evmCreate a wallet
import WalletManagerEvm from "@tetherto/wdk-wallet-evm";
const account = await new WalletManagerEvm(process.env.SEED_PHRASE, {
provider: "https://rpc.plasma.to", // or "https://rpc.stable.xyz"
}).getAccount();Register with x402
WalletAccountEvm satisfies the ClientEvmSigner interface directly. No adapter needed.
import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
const client = new x402Client();
registerExactEvmScheme(client, { signer: account });
const fetchWithPayment = wrapFetchWithPayment(fetch, client);Make a paid request
fetchWithPayment intercepts any 402 response, signs an EIP-3009 authorization with your WDK wallet, and retries automatically.
const response = await fetchWithPayment("https://api.example.com/weather", {
method: "GET",
});
const data = await response.json();
console.log("Response:", data);Your seed phrase controls your funds. Never commit it to version control. Use environment variables or a secrets manager.
Getting USD₮0 on Plasma or Stable
Before you can make x402 payments, your wallet needs USD₮0 on the target chain. If you hold USD₮ on Ethereum (or any supported EVM chain), bridge it using @tetherto/wdk-protocol-bridge-usdt0-evm.
The bridge uses LayerZero for secure cross-chain transfers. USD₮ on Ethereum is automatically converted to USD₮0 on the destination chain.
npm install @tetherto/wdk-wallet-evm @tetherto/wdk-protocol-bridge-usdt0-evmBridge USD₮ from Ethereum to Plasma / Stable
Create wallet and bridge protocol
import WalletManagerEvm from "@tetherto/wdk-wallet-evm";
import Usdt0ProtocolEvm from "@tetherto/wdk-protocol-bridge-usdt0-evm";
const account = await new WalletManagerEvm(process.env.SEED_PHRASE, {
provider: "https://eth.drpc.org",
}).getAccount();
const bridge = new Usdt0ProtocolEvm(account, {
bridgeMaxFee: 100000000000000n, // Max 0.0001 ETH in bridge fees
});Get a quote (recommended)
const USDT_ETHEREUM = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
const quote = await bridge.quoteBridge({
targetChain: "plasma", // or "stable"
recipient: await account.getAddress(),
token: USDT_ETHEREUM,
amount: 10000000n, // 10 USD₮ (6 decimals)
});
console.log("Total cost:", Number(quote.fee + quote.bridgeFee) / 1e18, "ETH");Execute the bridge
const result = await bridge.bridge({
targetChain: "plasma", // or "stable"
recipient: await account.getAddress(),
token: USDT_ETHEREUM,
amount: 10000000n,
});
console.log("Bridge tx:", result.hash);USD₮0 arrives on the destination chain within a few minutes.
You can bridge from any of 25+ supported EVM chains, not just Ethereum. Point your wallet at the source chain's RPC and use the USD₮ token address on that chain. See the full bridge module documentation.
Server: Accepting Payments (Hosted Facilitator)
Your server delegates verification and settlement to a hosted facilitator. You never interact with the chain directly.
About the Semantic facilitator: Semantic operates a public USD₮-enabled x402 facilitator at https://x402.semanticpay.io. This is a third-party service not operated, endorsed, or guaranteed by Tether.
The x402 protocol is an open standard. Anyone can build a facilitator or use one of their choice.
See the full working server example at x402/server.js.
npm install @tetherto/wdk-wallet-evm @x402/express @x402/evm @x402/core express dotenvCreate the seller wallet
import WalletManagerEvm from "@tetherto/wdk-wallet-evm";
const sellerAccount = await new WalletManagerEvm(process.env.SEED_PHRASE, {
provider: "https://rpc.plasma.to",
}).getAccount();
const sellerAddress = await sellerAccount.getAddress();Create the facilitator client
import { HTTPFacilitatorClient } from "@x402/core";
const facilitatorClient = new HTTPFacilitatorClient({
url: "https://x402.semanticpay.io/",
});Configure payment middleware
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
const PLASMA_NETWORK = "eip155:9745";
const USDT0_PLASMA = "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb";
const app = express();
app.use(
paymentMiddleware(
{
"GET /weather": {
accepts: [
{
scheme: "exact",
network: PLASMA_NETWORK,
price: {
amount: "1000", // $0.001 (6 decimals)
asset: USDT0_PLASMA,
extra: { name: "USDT0", version: "1", decimals: 6 },
},
payTo: sellerAddress,
},
],
description: "Weather data",
mimeType: "application/json",
},
},
new x402ResourceServer(facilitatorClient).register(
PLASMA_NETWORK,
new ExactEvmScheme(),
),
),
);The extra fields are passed to the buyer for EIP-712 signature construction. name and version must match what the on-chain USD₮0 contract expects.
Add your routes
// Gated - requires payment
app.get("/weather", (req, res) => {
res.json({ weather: "sunny", temperature: 70 });
});
// Not gated - no payment config
app.get("/health", (req, res) => {
res.json({ status: "ok" });
});
app.listen(4021);Routes not listed in the middleware config behave like normal Express routes.
Multi-Chain (Plasma + Stable)
To accept payments on both chains, add both networks to the accepts array and register both with the resource server. The buyer's client picks whichever network it has funds on.
const NETWORKS = {
plasma: { network: "eip155:9745", usdt0: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb" },
stable: { network: "eip155:988", usdt0: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736" },
};
const resourceServer = new x402ResourceServer(facilitatorClient)
.register(NETWORKS.plasma.network, new ExactEvmScheme())
.register(NETWORKS.stable.network, new ExactEvmScheme());Lifecycle Events
The Semantic facilitator supports an optional X-Event-Callback header. When provided, the facilitator POSTs real-time events to your callback URL during verification and settlement.
| Type | When | Key Fields |
|---|---|---|
verify_started | Facilitator begins verifying | details.network, details.checks |
verify_completed | Verification finished | details.isValid |
verify_failed | Verification error | details.error |
settle_started | Broadcasting on-chain transaction | details.network |
settle_completed | Transaction confirmed | details.transactionHash |
settle_failed | Settlement error | details.error |
const facilitatorClient = new HTTPFacilitatorClient({
url: "https://x402.semanticpay.io/",
fetch: (url, init) =>
fetch(url, {
...init,
headers: { ...init?.headers, "X-Event-Callback": "http://localhost:4021/payment-events" },
}),
});Events are fire-and-forget. If the callback URL is unreachable, events are silently dropped.
Server: Self-Hosted Facilitator (In-Process)
Instead of relying on a hosted facilitator, you can run verification and settlement in-process using the @semanticio/wdk-wallet-evm-x402-facilitator community module. This wraps a WDK wallet as an x402 FacilitatorEvmSigner. Your server handles the entire payment lifecycle locally.
Unlike the hosted Semantic facilitator (Plasma and Stable only), a self-hosted facilitator works with any EVM chain where USD₮0 is deployed. See the full deployment list at docs.usdt0.to.
@semanticio/wdk-wallet-evm-x402-facilitator is a community module developed and maintained by Semantic Pay. Tether does not endorse, audit, or assume responsibility for this module. It is currently in beta. Test thoroughly before using in production.
See the full working self-hosted server example at x402/server-inprocess.js.
npm install @semanticio/wdk-wallet-evm-x402-facilitator @tetherto/wdk-wallet-evm @x402/core @x402/evm @x402/express express dotenvCreate the facilitator signer
The facilitator wallet submits settlement transactions on-chain. It needs gas tokens on the target chain.
import WalletManagerEvm from "@tetherto/wdk-wallet-evm";
import WalletAccountEvmX402Facilitator from "@semanticio/wdk-wallet-evm-x402-facilitator";
const walletAccount = await new WalletManagerEvm(process.env.FACILITATOR_MNEMONIC, {
provider: process.env.RPC_URL, // Any EVM chain with USD₮0
}).getAccount();
const evmSigner = new WalletAccountEvmX402Facilitator(walletAccount);The facilitator wallet and the seller wallet can use different seed phrases. The facilitator pays gas; the seller receives USD₮. The facilitator wallet must have enough native token to pay gas.
Initialize the facilitator
import { x402Facilitator } from "@x402/core/facilitator";
import { registerExactEvmScheme } from "@x402/evm/exact/facilitator";
const facilitator = new x402Facilitator()
.onAfterVerify(async (ctx) => {
console.log("[verify]", ctx.result?.isValid ? "valid" : "invalid");
})
.onAfterSettle(async (ctx) => {
console.log("[settle] tx:", ctx.result?.transaction);
});
registerExactEvmScheme(facilitator, {
signer: evmSigner,
networks: process.env.NETWORK_ID, // e.g. "eip155:9745"
});Available hooks: onBeforeVerify, onAfterVerify, onBeforeSettle, onAfterSettle. All are async and receive a context object with the payment payload and result.
Wire into Express
Same paymentMiddleware pattern, but pass the in-process facilitator directly instead of an HTTPFacilitatorClient.
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
const NETWORK = process.env.NETWORK_ID || "eip155:9745";
const USDT0 = process.env.USDT0_ADDRESS || "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb";
const resourceServer = new x402ResourceServer(facilitator).register(
NETWORK,
new ExactEvmScheme(),
);
app.use(
paymentMiddleware(
{
"GET /weather": {
accepts: [{
scheme: "exact",
network: NETWORK,
price: { amount: "1000", asset: USDT0, extra: { name: "USDT0", version: "1", decimals: 6 } },
payTo: process.env.PAY_TO_ADDRESS,
}],
description: "Weather data",
mimeType: "application/json",
},
},
resourceServer,
),
);Summary
| Role | Packages | Notes |
|---|---|---|
| Buyer (Client) | @tetherto/wdk-wallet-evm, @x402/fetch, @x402/evm | WalletAccountEvm satisfies ClientEvmSigner directly. |
| Seller (Hosted) | @tetherto/wdk-wallet-evm, @x402/express, @x402/evm, @x402/core | Delegates to a hosted facilitator. Semantic supports Plasma and Stable. |
| Seller (Self-Hosted) | @tetherto/wdk-wallet-evm, @semanticio/wdk-wallet-evm-x402-facilitator, @x402/core, @x402/evm, @x402/express | In-process facilitator. Any USD₮0 chain. |
Resources
- x402 Protocol Spec - The open standard specification
- x402 GitHub - Reference implementations and examples
- Semantic Facilitator Docs - API reference for the hosted facilitator
- Self-Hosted Facilitator Module - Community in-process facilitator
- x402-usdt0 Demo - Full working buyer + seller demo
- WDK EVM Wallet Module - WDK EVM wallet documentation
- USD₮0 Deployments - Contract addresses on all chains
- EIP-3009 Specification - The authorization standard enabling gasless USD₮ transfers