Skip to main content

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.

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());

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. exact and upto are supported on both HPP networks. You can pass multiple facilitator clients to x402ResourceServer([...]) 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):

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.

Gateway / non-Node sellers

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 @x402 resource-server middleware as a Node sidecar behind your gateway and reuse its upto settlement as-is (nothing to reimplement).
  • Native — call the facilitator's /verify and /settle HTTP 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​