x402Flex Overview
What x402Flex does
x402Flex turns HTTP endpoints into paid resources using a deterministic onchain settlement model.
At a high level:
- Server returns a
402 Payment Requiredchallenge. - Client executes a payment that matches one accepted option.
- Server verifies settlement and unlocks the protected resource.
End-to-end 402 lifecycle
- Client calls a protected endpoint.
- Server responds with challenge payload (
x402Version,accepts, router intent metadata). - Client selects an accept option and executes payment.
- Client retries original request with payment authorization header.
- Server verifies
PaymentSettledV2and returns resource.
Build a seller challenge
What this does: creates a challenge body with deterministic router payment fields.
import { buildFlexResponse } from '@pepay/x402flex';
const MERCHANT_ADDRESS = '0x1111111111111111111111111111111111111111';
const ROUTER_ADDRESS = '0xf14f56A54E0540768b7bC9877BDa7a3FB9e66E91';
const TOKEN_ADDRESS = '0x55d398326f99059fF775485246999027B3197955';
const REFERENCE_ID = 'api-credit-001';
const challenge = buildFlexResponse({
merchant: MERCHANT_ADDRESS,
referenceId: REFERENCE_ID,
ttlSeconds: 600,
accepts: [
{
scheme: 'exact:evm:permit2',
network: 'bnbTestnet',
chainId: 97,
asset: TOKEN_ADDRESS,
amount: '1000000',
payTo: MERCHANT_ADDRESS,
router: {
address: ROUTER_ADDRESS,
merchant: MERCHANT_ADDRESS,
token: TOKEN_ADDRESS,
},
metadata: { product: 'api-credit' },
},
],
});
Expected result: one canonical challenge payload you can return with HTTP 402.
Common errors and fixes:
- Missing
chainIdin accept option. - Missing router address for router-enabled payment schemes.
Submit buyer authorization
What this does: executes the payment matching the selected challenge option.
import { sendRouterPayment, RpcTransport } from '@pepay/x402flex';
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider(process.env.BNB_RPC_URL);
const signer = new ethers.Wallet(process.env.PAYER_PRIVATE_KEY!, provider);
const accept = challenge.accepts[0];
if (!accept?.router) throw new Error('Challenge missing router payload');
const paymentResult = await sendRouterPayment({
transport: new RpcTransport(signer),
routerAddress: accept.router.address,
intent: {
...accept.router.intent,
amount: BigInt(accept.router.intent.amount),
},
witness: {
schemeId: accept.router.witness?.schemeId ?? accept.schemeId,
intentHash: accept.router.witness?.intentHash ?? ethers.ZeroHash,
payer: accept.router.witness?.payer ?? signer.address,
salt: accept.router.witness?.salt ?? ethers.ZeroHash,
},
reference: accept.reference,
});
console.log(paymentResult.txHash);
Expected result: transaction hash for the payment used as authorization proof.
Common errors and fixes:
- Invalid witness fields (scheme mismatch or wrong intent hash).
- Reference mismatch between challenge and submitted payment.
Verify and unlock the resource
What this does: verifies settlement and grants access.
import { createApiClient } from '@pepay/x402flex';
const api = createApiClient({ baseUrl: 'https://api.bnbpay.org' });
const settlement = await api.payments.get(challenge.accepts[0].router!.intent.paymentId);
if (settlement.paymentId) {
// unlock resource response
}
Expected result: server can deterministically map settlement to requested resource.
Common errors and fixes:
- Wrong network in challenge vs execution.
- Insufficient confirmations for your security threshold.
Core invariants for x402 correctness
paymentIdmust match the exact intent fields used onchain.referenceHashmust be computed from finalreferenceDatastring.nonceis part of the deterministic binding and must be retained.- Session flows require tagged references and valid session auth data.