Skip to content

The x402 payment, step by step

Capital Press uses x402 version 2 with the exact scheme. The token is USDG on Robinhood Chain (eip155:4663), moved with a Permit2 witness transfer. Any compatible x402 client works. This page shows what happens underneath.

One time: approve Permit2

USDG doesn't support gasless signed transfers natively, so payments go through the standard Permit2 contract. Approve it once:

USDG.approve(0x000000000022D473030F116dDEE9F6B43aC78BA3, max)

This is the only transaction your wallet ever sends. You can send it through any Robinhood Chain RPC, including /api/rpc.

Each purchase is two payments

OrderEndpointAmountRecipient
1POST /api/pay/treasury10% of priceUnits, rounded downPlatform treasury
2POST /api/pay/publisherThe remaining 90%The owner of the press

Both take the same body: { "pressId": 322 }. Pay the fee first. The publisher endpoint answers 409 until the fee is recorded for your wallet.

The exchange

1. Ask. POST without a payment. The server answers 402:

json
{
  "x402Version": 2,
  "error": "Payment is required (…).",
  "accepts": [{
    "scheme": "exact",
    "network": "eip155:4663",
    "amount": "50000",
    "asset": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168",
    "payTo": "0x…",
    "maxTimeoutSeconds": 300,
    "extra": { "assetTransferMethod": "permit2", "spender": "0x402085c248EeA27D92E8b30b2C58ed07f9E20001", "name": "USDG" }
  }],
  "resource": { "url": "https://capitalpress.io/api/pay/treasury" }
}

The same document is also sent base64-encoded in the payment-required header.

2. Sign. Sign this EIP-712 message with the paying wallet:

Domain{ name: "Permit2", chainId: 4663, verifyingContract: 0x000000000022D473030F116dDEE9F6B43aC78BA3 }
Primary typePermitWitnessTransferFrom
permitted{ token: asset, amount }
spenderextra.spender
nonceA random 256-bit number
deadlineNow plus a few minutes, in seconds
witness{ to: payTo, validAfter: 0 }

Types:

PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,Witness witness)
TokenPermissions(address token,uint256 amount)
Witness(address to,uint256 validAfter)

The recipient is inside the signed witness, so the payment can only ever go to payTo.

3. Pay. Repeat the same request with the same body, adding the header payment-signature: a base64-encoded JSON payload.

json
{
  "x402Version": 2,
  "accepted": { "…the accepts entry you chose…" },
  "resource": { "url": "…" },
  "payload": {
    "signature": "0x…",
    "permit2Authorization": {
      "permitted": { "token": "0x5fc5…d168", "amount": "50000" },
      "from": "0xYourWallet",
      "spender": "0x4020…0001",
      "nonce": "…",
      "deadline": "…",
      "witness": { "to": "0x…", "validAfter": "0" }
    }
  }
}

4. Settled. The server verifies the signature, settles it onchain (a relayer pays the gas) and answers 200. The payment-response header carries a base64 receipt with the transaction hash.

json
{ "paid": true, "tx": "0x…" }

The publisher payment's response also includes "body", the full text.

Responses you may see

StatusMeaning
402 with permit2_insufficient_balanceThe wallet doesn't hold enough USDG
402 "doesn't match the current price or recipient"You signed different terms from the ones offered. Fetch a fresh challenge
402 "Malformed payment header"The header isn't valid base64 JSON
404No live press has that pressId
409 "Pay the platform fee first"Make the treasury payment before the publisher payment
409 "already unlocked"This wallet already bought this press
503Payments are temporarily unavailable

Rules worth enforcing in your agent

  • Check asset is USDG and amount is no more than the press's priceUnits before signing.
  • Never put ids in the URL of a paid endpoint. They go in the body, and the body is resent with the payment.
  • A signed payment expires with its deadline. Sign right before you send.

Nothing published on Capital Press is investment advice.