💸 x402 Payment Protocol
x402 is available to BCW plan users. Contact help@arkhia.io to request access.
x402 Payment Integration via Arkhia
April 2026
Arkhia now supports the x402 Payment Protocol — an open standard for machine-to-machine HTTP payments using USDC on EVM-compatible chains. Route your x402 payment facilitation requests through Arkhia's authenticated proxy for usage tracking, rate limiting, and credit-based billing.
What is x402?
x402 is an HTTP-native payment protocol where the server responds with a 402 Payment Required status and payment instructions. The client creates an ERC-3009 TransferWithAuthorization signature and submits it to a facilitator, which executes the on-chain USDC transfer.
Arkhia acts as an authenticated proxy between your application and the x402 facilitator service — your API key is part of the URL, enabling per-project usage tracking.
🌐 URL Format
https://starter.arkhia.io/x402/{network}/{api-key}/{endpoint}
| Segment | Values | Description |
|---|---|---|
network | mainnet, testnet | Target network environment |
api-key | Your project API key | From the Arkhia dashboard |
endpoint | supported, health, settle, facilitate | Facilitator endpoint |
Example:
https://starter.arkhia.io/x402/testnet/YOUR_API_KEY/supported
🛠️ Prerequisites
- An Arkhia account with x402 access enabled
- A project created in the Arkhia Dashboard — the network will show x402 Payment
- Your project API key from the Project Details → Endpoints section
- A wallet with Sepolia USDC (testnet) or mainnet USDC for live payments
📡 Endpoints
GET /supported
Returns the payment schemes and networks the facilitator supports.
curl "https://starter.arkhia.io/x402/testnet/YOUR_API_KEY/supported"
Response:
{
"kinds": [
{ "x402Version": 1, "scheme": "exact", "network": "sepolia" },
{ "x402Version": 2, "scheme": "exact", "network": "eip155:11155111" }
]
}
GET /health
Check that the facilitator service is running.
curl "https://starter.arkhia.io/x402/testnet/YOUR_API_KEY/health"
Response:
{ "status": "ok" }
POST /settle
Submit a signed ERC-3009 authorization for on-chain settlement. This is the core endpoint of the x402 flow.
🔄 Settlement Flow
Your App → GET /supported → Get facilitator address & payment requirements
Your App → Sign ERC-3009 authorization (TransferWithAuthorization)
Your App → POST /settle → Arkhia proxy → x402 facilitator → Sepolia/mainnet USDC transfer
💻 Code Examples
- cURL
- Python
- JavaScript
# 1. Check supported schemes
curl "https://starter.arkhia.io/x402/testnet/YOUR_API_KEY/supported"
# 2. Submit a settlement (v1 format)
curl -X POST "https://starter.arkhia.io/x402/testnet/YOUR_API_KEY/settle" \
-H "Content-Type: application/json" \
-d '{
"x402Version": 1,
"paymentPayload": {
"x402Version": 1,
"scheme": "exact",
"network": "sepolia",
"payload": {
"authorization": {
"from": "0xPAYER_ADDRESS",
"to": "0xFACILITATOR_ADDRESS",
"value": "1000000",
"validAfter": 1700000000,
"validBefore": 1700003600,
"nonce": "0xRANDOM_NONCE_32_BYTES"
},
"signature": "0xSIGNED_ERC3009_SIGNATURE"
}
},
"paymentRequirements": {
"scheme": "exact",
"network": "sepolia",
"maxAmountRequired": "1000000",
"asset": "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
"payTo": "0xFACILITATOR_ADDRESS"
}
}'
import os
import time
import secrets
import asyncio
import httpx
from eth_account import Account
from eth_account.messages import encode_typed_data
from web3 import Web3, AsyncWeb3
FACILITATOR_URL = "https://starter.arkhia.io/x402/testnet/YOUR_API_KEY"
RPC_URL = "https://ethereum-sepolia-rpc.publicnode.com"
PAYER_PRIVATE_KEY = os.getenv("PAYER_PRIVATE_KEY")
USDC_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"
FACILITATOR_ADDRESS = "0x5F0ddADAdb6938C96c6D0C4Fa3708D06306e5141"
MINIMAL_USDC_ABI = [
{"inputs": [], "name": "name", "outputs": [{"type": "string"}], "stateMutability": "view", "type": "function"},
{"inputs": [], "name": "DOMAIN_SEPARATOR", "outputs": [{"type": "bytes32"}], "stateMutability": "view", "type": "function"},
]
async def sign_erc3009(w3, token_address, private_key, authorization):
account = Account.from_key(private_key)
contract = w3.eth.contract(address=Web3.to_checksum_address(token_address), abi=MINIMAL_USDC_ABI)
token_name = await contract.functions.name().call()
chain_id = await w3.eth.chain_id
signable = encode_typed_data(
domain_data={"name": token_name, "version": "2", "chainId": chain_id, "verifyingContract": Web3.to_checksum_address(token_address)},
message_types={"TransferWithAuthorization": [
{"name": "from", "type": "address"}, {"name": "to", "type": "address"},
{"name": "value", "type": "uint256"}, {"name": "validAfter", "type": "uint256"},
{"name": "validBefore", "type": "uint256"}, {"name": "nonce", "type": "bytes32"},
]},
message_data={
"from": Web3.to_checksum_address(authorization["from"]),
"to": Web3.to_checksum_address(authorization["to"]),
"value": int(authorization["value"]),
"validAfter": int(authorization["validAfter"]),
"validBefore": int(authorization["validBefore"]),
"nonce": Web3.to_bytes(hexstr=authorization["nonce"]),
},
)
return account.sign_message(signable).signature.hex()
async def settle():
w3 = AsyncWeb3(AsyncWeb3.AsyncHTTPProvider(RPC_URL))
account = Account.from_key(PAYER_PRIVATE_KEY)
now = int(time.time())
authorization = {
"from": account.address,
"to": FACILITATOR_ADDRESS,
"value": str(1 * 10**6), # 1 USDC
"validAfter": now - 60,
"validBefore": now + 3600,
"nonce": "0x" + secrets.token_hex(32),
}
signature = await sign_erc3009(w3, USDC_ADDRESS, PAYER_PRIVATE_KEY, authorization)
payload = {
"x402Version": 1,
"paymentPayload": {
"x402Version": 1, "scheme": "exact", "network": "sepolia",
"payload": {"authorization": authorization, "signature": signature},
},
"paymentRequirements": {
"scheme": "exact", "network": "sepolia",
"maxAmountRequired": authorization["value"],
"asset": USDC_ADDRESS, "payTo": FACILITATOR_ADDRESS,
},
}
async with httpx.AsyncClient(timeout=60.0) as client:
resp = await client.post(f"{FACILITATOR_URL}/settle", json=payload)
result = resp.json()
if result.get("success"):
print(f"Settlement successful! TX: {result.get('transaction')}")
else:
print(f"Settlement failed: {result.get('errorReason')}")
asyncio.run(settle())
import axios from 'axios';
import { ethers } from 'ethers';
const FACILITATOR_URL = 'https://starter.arkhia.io/x402/testnet/YOUR_API_KEY';
const RPC_URL = 'https://ethereum-sepolia-rpc.publicnode.com';
const USDC_ADDRESS = '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238';
const FACILITATOR_ADDRESS = '0x5F0ddADAdb6938C96c6D0C4Fa3708D06306e5141';
const USDC_ABI = ['function name() view returns (string)'];
async function settle(privateKey) {
const provider = new ethers.JsonRpcProvider(RPC_URL);
const wallet = new ethers.Wallet(privateKey, provider);
const usdc = new ethers.Contract(USDC_ADDRESS, USDC_ABI, provider);
const { chainId } = await provider.getNetwork();
const tokenName = await usdc.name();
const now = Math.floor(Date.now() / 1000);
const nonce = ethers.hexlify(ethers.randomBytes(32));
const domain = { name: tokenName, version: '2', chainId, verifyingContract: USDC_ADDRESS };
const types = {
TransferWithAuthorization: [
{ name: 'from', type: 'address' }, { name: 'to', type: 'address' },
{ name: 'value', type: 'uint256' }, { name: 'validAfter', type: 'uint256' },
{ name: 'validBefore', type: 'uint256' }, { name: 'nonce', type: 'bytes32' },
],
};
const value = { from: wallet.address, to: FACILITATOR_ADDRESS, value: 1_000_000n, validAfter: now - 60, validBefore: now + 3600, nonce };
const signature = await wallet.signTypedData(domain, types, value);
const payload = {
x402Version: 1,
paymentPayload: {
x402Version: 1, scheme: 'exact', network: 'sepolia',
payload: { authorization: { ...value, value: value.value.toString() }, signature },
},
paymentRequirements: {
scheme: 'exact', network: 'sepolia',
maxAmountRequired: '1000000', asset: USDC_ADDRESS, payTo: FACILITATOR_ADDRESS,
},
};
const { data } = await axios.post(`${FACILITATOR_URL}/settle`, payload);
if (data.success) {
console.log('Settlement successful! TX:', data.transaction);
} else {
console.error('Settlement failed:', data.errorReason);
}
}
settle(process.env.PAYER_PRIVATE_KEY);
💳 Credit Tracking
All x402 requests are tracked in the Arkhia credit system:
- Each request to the x402 proxy consumes credits from your plan
- Usage is visible in the Analytics section of your Arkhia dashboard per project
- x402 requests appear as API type
X402in the usage breakdown
🔍 Supported Networks
| Network | Protocol Identifier | Environment |
|---|---|---|
| Sepolia (EVM testnet) | eip155:11155111 / sepolia | Testnet |
| Ethereum Mainnet | eip155:1 | Mainnet |
❓ Troubleshooting
| Error | Cause | Fix |
|---|---|---|
Unauthorized access to x402 Payment service | Project not enabled for x402 | Contact support to enable x402 on your plan |
ERC20: transfer amount exceeds balance | Payer wallet has insufficient USDC | Fund the wallet with Sepolia USDC from the Circle faucet |
x402 request body is required | POST body is empty or missing | Ensure you send a valid JSON payload |
x402 Payment request failed | Upstream facilitator error | Check your network selection and payment payload structure |