Quickstart: Sellers
Put a price on an HTTP route and let HPP's facilitator handle verification and onchain settlement.
This example uses the @x402/express middleware, but the same pieces apply to any HTTP framework
the SDK supports.
1. Install
npm install @x402/express @x402/core @x402/evm @x402/extensions express
2. Register USDC.e
HPP's USDC.e is registered in the SDK's default-asset map so the exact (EIP-3009) domain
(Bridged USDC / version 2 / 6 decimals) is filled in automatically. Run this once at startup,
before constructing the resource server:
import { DEFAULT_STABLECOINS } from "@x402/evm";
const USDC_E = {
address: "0x401eCb1D350407f13ba348573E5630B83638E30D",
name: "Bridged USDC",
version: "2",
decimals: 6,
};
DEFAULT_STABLECOINS["eip155:190415"] = USDC_E; // HPP Mainnet
DEFAULT_STABLECOINS["eip155:181228"] = USDC_E; // HPP Sepolia
3. Build the resource server
Point a facilitator client at HPP's hosted facilitator and register the schemes you want to accept on each network.
- HPP Mainnet
- HPP Sepolia
import { x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { UptoEvmScheme } from "@x402/evm/upto/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
const facilitator = new HTTPFacilitatorClient({ url: "https://facilitator.hpp.io" });
const resourceServer = new x402ResourceServer([facilitator]);
resourceServer.register("eip155:190415", new ExactEvmScheme());
resourceServer.register("eip155:190415", new UptoEvmScheme());
import { x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { UptoEvmScheme } from "@x402/evm/upto/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
const facilitator = new HTTPFacilitatorClient({ url: "https://facilitator-sepolia.hpp.io" });
const resourceServer = new x402ResourceServer([facilitator]);
resourceServer.register("eip155:181228", new ExactEvmScheme());
resourceServer.register("eip155:181228", new UptoEvmScheme());
Only register what the facilitator supports. The resource server throws if you advertise a
(network, scheme)pair that the facilitator does not return from/supported.exactanduptoare supported on both HPP networks. You can pass multiple facilitator clients tox402ResourceServer([...])to serve more than one network from a single process.
4. Price your routes and mount the middleware
Each route lists one or more accepts (one per (network, scheme)), priced in USDC.e base units.
import express from "express";
import { paymentMiddleware } from "@x402/express";
import type { RoutesConfig } from "@x402/core/server";
const PAY_TO = "0xYourReceivingAddress"; // where settled USDC.e lands
const routes: RoutesConfig = {
"POST /paid/hello": {
description: "A paid hello-world endpoint.",
accepts: [
{
scheme: "exact",
network: "eip155:190415",
payTo: PAY_TO,
price: {
amount: "10000", // 0.01 USDC.e (6 decimals)
asset: "0x401eCb1D350407f13ba348573E5630B83638E30D",
extra: { name: "Bridged USDC", version: "2" },
},
maxTimeoutSeconds: 600,
},
],
},
};
const app = express();
app.use(paymentMiddleware(routes, resourceServer));
app.post("/paid/hello", (_req, res) => {
res.json({ message: "Thanks for paying — here is your resource." });
});
app.listen(4021, () => console.log("x402 resource server on :4021"));
That's it. An unpaid POST /paid/hello now returns 402 with your terms; a request carrying a valid
payment-signature header is verified, served, and settled onchain — and the handler returns the resource.
Accepting upto and gasless payments
To also accept the upto scheme, list it in the route's accepts (in priority order — the buyer's
SDK picks the first it supports) and register it on the network alongside exact.
For gasless upto — letting a buyer with zero native ETH pay — the route must declare the
EIP-2612 gas-sponsoring extension. Without it, the facilitator cannot sponsor the buyer's Permit2
approval, and a buyer without a standing allowance is rejected with permit2_allowance_required.
import { declareEip2612GasSponsoringExtension } from "@x402/extensions";
// Replaces the route from step 4. `PAY_TO` and the `price` object are the same
// as before (here `price` is the { amount, asset, extra } object shown above).
const routes: RoutesConfig = {
"POST /paid/hello": {
description: "A paid hello-world endpoint.",
accepts: [
{ scheme: "exact", network: "eip155:190415", payTo: PAY_TO, price, maxTimeoutSeconds: 600 },
{ scheme: "upto", network: "eip155:190415", payTo: PAY_TO, price, maxTimeoutSeconds: 600 },
],
// Required for gasless `upto`: lets the facilitator sponsor the buyer's
// one-time Permit2 approval via EIP-2612 (so a 0-ETH wallet can pay).
extensions: { ...declareEip2612GasSponsoringExtension() },
},
};
The facilitator must advertise eip2612GasSponsoring for the network (HPP Mainnet and Sepolia both
do — see Facilitator → Gasless settlement).
Settling the actual amount (metered billing)
Advertising upto (above) only sets the maximum. The point of upto is that you settle only
what was actually used, computed after the work runs — which is what makes it the right scheme
for usage-based pricing: LLM token counts, compute time, bytes served. The advertised price is
the cap; the final charge is always ≤ the authorized max, and settling 0 costs no onchain
transaction at all.
After your handler runs, tell the middleware how much to actually charge with
setSettlementOverrides (exported from @x402/express) — it settles that amount instead of the
advertised max:
import { setSettlementOverrides } from "@x402/express";
// A metered route: charge for what the model actually used.
app.post("/paid/llm", async (req, res) => {
// The `upto` payment is already verified by the middleware;
// settlement runs AFTER this handler returns.
const result = await runModel(req.body); // do the metered work
// Your usage → USDC.e atomic units, as a STRING, always ≤ the advertised max.
const chargeAtomic = usageToUsdcE(result.usage); // e.g. "1000" = 0.001 USDC.e
// Settle the actual amount instead of the max. Pass "0" and nothing settles onchain.
setSettlementOverrides(res, { amount: chargeAtomic });
res.json(result); // 200 → the facilitator settles `chargeAtomic` (≤ max) onchain
});
amount is a string of atomic USDC.e units. The SDK also accepts percentage and dollar-price
override forms, and there's more to the scheme semantics — for those we point at the upstream docs
rather than duplicate them (they track the SDK, which changes often):
- x402 seller quickstart — Payment Schemes —
setSettlementOverridesforms. - x402 Payment Schemes overview — scheme semantics.
What's HPP-specific: both HPP facilitators support upto with variable settlement
(settled ≤ max) on HPP Mainnet (eip155:190415) and HPP Sepolia (eip155:181228) — you can
confirm it live at GET https://facilitator.hpp.io/supported (and …-sepolia). Once the SDK is
pointed at the HPP facilitator + USDC.e (steps 2–3), the upstream upto recipe settles onchain on
HPP unchanged.
Building the seller on an API gateway (Kong/Lua) or another language? The metering lives in whatever layer counts usage. Two paths:
- Sidecar (recommended) — run the
@x402resource-server middleware as a Node sidecar behind your gateway and reuse itsuptosettlement as-is (nothing to reimplement). - Native — call the facilitator's
/verifyand/settleHTTP endpoints directly from your language. The wire contract is the standard x402 protocol; see Facilitator.
Reacting to settlement
res.json() returns right away, but the middleware sends the response only after it settles (and
only on success — status < 400). Your handler can't see the settle result inline. To run logic
after settlement — record usage, credit an account, emit a webhook — register hooks on the resource
server:
resourceServer.onAfterSettle(async (ctx) => {
// ctx.result carries the settlement outcome (success, onchain tx, payer).
// e.g. record the charge or emit a webhook.
});
resourceServer.onSettleFailure(async (ctx) => {
// The work ran but settlement failed — the buyer got an error, not the resource.
// e.g. log it for reconciliation.
});
The full hook set is in the upstream lifecycle hooks reference.
Next
- Quickstart: Buyers — pay this endpoint from a client or agent.
- Facilitator — endpoints, supported schemes, and self-hosting.