integrating p402
p402 turns any HTTP resource into a machine-payable one. this page documents exactly what runs in this deployment — not a future spec.
overview
a protected resource answers an unpaid request with HTTP 402 and x402 v2 requirements. the client signs a payment authorization and retries with an X-PAYMENT header. the p402 facilitator verifies the authorization against chain state, settles it, and only then does the resource release its body along with an X-PAYMENT-RESPONSE receipt.
networks
mainnet
chain id 4663 · eip155:4663
testnet
chain id 46630 · eip155:46630
all amounts are token base units expressed as decimal strings and handled as bigint on the server. p402 never converts prices through floating point.
the 402 handshake
{
"x402Version": 2,
"accepts": [
{
"scheme": "exact",
"network": "eip155:4663",
"asset": "0xTokenAddress",
"amount": "1000",
"payTo": "0xRecipient",
"maxTimeoutSeconds": 60
}
],
"resource": {
"url": "https://<your-app>/api/demo/weather",
"description": "Live p402 weather demo",
"mimeType": "application/json"
},
"error": "payment_required"
}facilitator api
- GET /api/facilitator/health — live rpc, relayer and configuration state
- GET /api/facilitator/supported — schemes, networks and allowlisted assets
- POST /api/facilitator/verify — validate a payment payload without settling
- POST /api/facilitator/settle — settle and record a payment
POST /api/facilitator/verify
content-type: application/json
{
"paymentPayload": { "scheme": "exact", "network": "eip155:4663", "payload": { … } },
"paymentRequirements": { "scheme": "exact", "network": "eip155:4663", "asset": "0x…", "amount": "1000", "payTo": "0x…" }
}
200 { "isValid": true }
200 { "isValid": false, "invalidReason": "amount_mismatch" }POST /api/facilitator/settle
{
"paymentPayload": { … },
"paymentRequirements": { … },
"projectId": "optional uuid",
"idempotencyKey": "optional string"
}
200 { "success": true, "transaction": "0x…", "network": "eip155:4663", "payer": "0x…" }
402 { "success": false, "errorReason": "settlement_failed" }protecting a resource
import { createP402Handler } from "@p402/middleware";
export const handler = createP402Handler(
{
accepts: [
{
scheme: "exact",
network: "eip155:4663",
asset: process.env.P402_ASSET,
amount: "1000", // base units, never floats
payTo: process.env.P402_RECIPIENT,
maxTimeoutSeconds: 60,
},
],
resource: { url: "https://api.example.com/report", mimeType: "application/json" },
facilitatorUrl: "https://<your-app>/api/facilitator",
},
async (request, { settlement }) =>
Response.json({ report: "…", txHash: settlement.transaction }),
);settlement adapters
eip-3009
transferWithAuthorization. the signed authorization is checked for expiry and nonce state before submission.
available when the asset implements EIP-3009
permit2
signature-transfer path for assets without EIP-3009.
returns a configuration error until a verified Permit2 deployment and asset certification exist
direct transfer
the client submits its own transfer and supplies the transaction hash; p402 re-derives sender, receiver, asset and amount from the receipt logs.
test and demo use only
security model
- — a client-supplied transaction hash is never trusted; the receipt is re-read from the chain.
- — nonces, authorization hashes and transaction hashes are stored uniquely, so a proof cannot be replayed.
- — proofs are reserved before settlement, making settle idempotent under concurrency.
- — creator origins and webhook targets are SSRF-checked: no loopback, private ranges, link-local, or metadata hosts; redirects blocked; timeouts, size caps and content-type allowlists enforced.
- — facilitator endpoints are rate limited per client and return sanitized errors.
- — wallet sign-in is a server-verified signature over a single-use nonce; a connected address alone never authenticates.
webhooks
each webhook endpoint gets a secret shown once. deliveries are signed with HMAC-SHA256 over the raw body and carry a timestamp; verify with a timing-safe comparison and reject stale timestamps.
X-P402-Signature: t=<unix-seconds>,v1=<hex hmac_sha256(secret, "t.<raw body>")>configuration
ROBINHOOD_RPC_URL= # json-rpc endpoint
ROBINHOOD_CHAIN_ID= # 4663 or 46630
P402_ALLOWED_ASSETS= # comma separated token addresses
P402_MIN_CONFIRMATIONS=1
P402_FACILITATOR_PRIVATE_KEY= # low-balance dedicated relayer only
VITE_PONS_FACTORY_ADDRESS= # required for token launchwhen any of these are missing, p402 shows a "not configured" state instead of a fake success. deploy to mainnet only after an independent security review.