RWA integration coming soon
p402
documentation

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

402 response body
{
  "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
verify
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" }
settle
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

fetch-compatible middleware
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.

header
X-P402-Signature: t=<unix-seconds>,v1=<hex hmac_sha256(secret, "t.<raw body>")>

configuration

server environment
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 launch

when 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.