x402
x402 lets an HTTP API ask for payment with a 402 response. The client pays with a signed stablecoin transfer and retries.
HalvorAI records both sides: the agent paying, and the server being paid.
Packages
Section titled “Packages”HalvorAI works with the x402 Foundation packages for protocol version 2:
| Role | Package |
|---|---|
| Client fetch wrapper | @x402/fetch |
| Client core and EVM scheme | @x402/core, @x402/evm |
| Hono resource server | @x402/hono |
| Express resource server | @x402/express |
The three headers carry base64-encoded JSON:
| Header | Direction | Contains |
|---|---|---|
PAYMENT-REQUIRED | server to client | What the server wants, the PaymentRequired object |
PAYMENT-SIGNATURE | client to server | The signed PaymentPayload |
PAYMENT-RESPONSE | server to client | The SettlementResponse |
The paying agent
Section titled “The paying agent”Wrap the inner fetch, so the retry that carries PAYMENT-SIGNATURE is seen:
import { wrapFetchWithPayment } from '@x402/fetch';import { HalvorClient, withAgentContext } from '@halvorai/ingest';import { withHalvor } from '@halvorai/ingest/x402';
const halvor = new HalvorClient();const fetchPaid = wrapFetchWithPayment(withHalvor(fetch, halvor), x402Client);
await withAgentContext( { agent: { id: 'agt_research_07' }, owner: { id: 'own_acme_ltd', type: 'business' }, instruction: { id: 'ins_5521', text: 'Pull the latest EU power prices' }, }, async () => { const res = await fetchPaid('https://api.example.com/premium-data'); },);withHalvor observes each request and emits:
| It sees | It emits |
|---|---|
A 402 with PAYMENT-REQUIRED | payment.requested |
A request carrying PAYMENT-SIGNATURE | payment.authorized, with the decoded payload as x402.payment_payload evidence |
A response with PAYMENT-RESPONSE and success | payment.settled, with the x402.settlement evidence |
A response with PAYMENT-RESPONSE and a failure | payment.failed, with failure_reason from the settlement’s error reason |
A header it cannot decode is reported to onError and the request continues untouched.
The resource server
Section titled “The resource server”If you sell an API to agents, observe the same headers on your side:
import { paymentMiddleware } from '@x402/hono';import { halvorX402Hono } from '@halvorai/ingest/x402';
app.use('*', halvorX402Hono(halvor, { payee: { name: 'Example Market Data', wallet: { network: 'eip155:8453', address: '0x209693Bc6afc0C5328bA36FaF03C514EF312287C' }, resource_url: 'https://api.example.com/premium-data', },}));app.use('*', paymentMiddleware(/* your x402 configuration */));halvorX402Express(halvor, options) does the same for Express. Events use the x402-server adapter and your options as the
payee. The agent id is taken from Visa TAP or x402 extension data when the request carries it, and otherwise from the payer’s
wallet address.
How fields are mapped
Section titled “How fields are mapped”| x402 | APE |
|---|---|
resource.url | payee.resource_url |
payTo | payee.wallet.address, with payee.type set to api_provider when a resource is named |
network (a CAIP-2 id such as eip155:8453) | payment.rail_ref.network, the asset network and the payee wallet network |
asset | the asset address |
amount | payment.amount.value, in atomic units |
scheme | payment.rail_ref.scheme |
authorization.nonce | payment.rail_ref.nonce |
authorization.from | instrument.wallet.address |
settlement transaction | payment.rail_ref.tx_hash |
The payment id is x402:<network>:<nonce>, so the three events of one payment link together.
Amounts are integers in atomic units. For USDC, which has six decimals, "10000" is 0.01 USDC. HalvorAI converts pegged
stablecoins to US dollars for reports.
Check it works
Section titled “Check it works”On a test network such as Base Sepolia, make one payment and open Payments. You should see payment.requested,
payment.authorized and payment.settled for the same payment id, and the settlement evidence in the drawer. A failed
settlement shows as payment.failed with the facilitator’s reason.