Docs
Dashboard

x402 Protocol

Monetize any API with pay-per-request billing using the HTTP 402 Payment Required standard. GullyPay acts as the x402 facilitator, handling payment verification, settlement, and receipt generation.

What is x402?

The x402 protocol extends HTTP with native payment semantics. When a client requests a paid resource, the server responds with 402 Payment Required and includes payment requirements in the X-Payment header. The client pays through a facilitator (GullyPay), receives a receipt, and retries the request with the receipt attached.

This enables AI agents, developers, and applications to pay for API access programmatically without subscriptions or manual billing setup. Note: the facilitator settlement endpoints (POST /x402/pay, /x402/verify, /x402/refund) require a scoped GullyPay API key (Authorization: Bearer rk_… or x-api-key); only GET /x402/status and GET /x402/requirements/:resource are public.

Reference Implementation

The x402 protocol is an open standard. See the reference implementation at github.com/jinklabs/x402 (opens in a new tab).

How It Works

x402 Payment Flow
Client                    API Server                 GullyPay Facilitator
|                           |                              |
|  1. GET /api/resource     |                              |
|-------------------------->|                              |
|                           |                              |
|  2. 402 Payment Required  |                              |
|  X-Payment: {requirements}|                              |
|<--------------------------|                              |
|                           |                              |
|  3. POST /x402/pay                                       |
|  {resource, txHash, amount, network, payerAddress}        |
|--------------------------------------------------------->|
|                           |                              |
|  4. {receiptToken}                                       |
|<---------------------------------------------------------|
|                           |                              |
|  5. GET /api/resource     |                              |
|  X-Payment-Receipt: token |                              |
|-------------------------->|                              |
|                           |  6. POST /x402/verify        |
|                           |  {receiptToken}              |
|                           |----------------------------->|
|                           |                              |
|                           |  7. {valid: true}            |
|                           |<-----------------------------|
|                           |                              |
|  8. 200 OK + resource     |                              |
|<--------------------------|                              |

Step-by-step breakdown:

  1. Client requests a resource -- a standard HTTP request to any API endpoint.
  2. Server returns 402 -- the response includes an X-Payment header with pricing, accepted currencies, and the facilitator URL.
  3. Client pays -- the client sends payment details (on-chain transaction hash or card payment) to the GullyPay facilitator.
  4. Facilitator issues receipt -- after verifying the payment, GullyPay returns a signed receipt token.
  5. Client retries with receipt -- the original request is retried with the X-Payment-Receipt header.
  6. Server verifies receipt -- the server calls GullyPay to verify the receipt is valid, unused, and covers the required amount.
  7. Facilitator confirms -- returns verification result with payment details.
  8. Server serves resource -- the paid resource is returned to the client.

Facilitator Endpoints

GET/x402/statusGet facilitator status, supported schemes, and networks
GET/x402/requirements/:resourceGet payment requirements (returns 402 with X-Payment header)
POST/x402/paySubmit payment and receive a receipt token
POST/x402/verifyVerify a receipt token is valid
POST/x402/refundRequest a refund for a receipt

GET /x402/status

Returns the facilitator's capabilities and supported payment networks. Gully Chain Testnet (gully-chain-testnet, chain id 485590) is listed, with its RPC, explorer and accepted assets under networks, only while the API runs with GULLY_CHAIN_ENABLED=true. While the flag is off it is not advertised, endpoints on it cannot be registered and payments on it are rejected.

Response
{
"facilitator": "gullypay",
"x402Version": 1,
"supportedSchemes": ["exact"],
"supportedNetworks": ["base", "base-sepolia", "ethereum", "bsc", "solana", "gully-chain-testnet"],
"supportedAssets": ["USDC", "USDT", "GLUSD", "ETH", "WETH"],
"networks": {
  "gully-chain-testnet": {
    "network": "gully-chain-testnet",
    "chainId": 485590,
    "caip2": "eip155:485590",
    "name": "Gully Chain Testnet",
    "rpcUrl": "https://testnet-rpc.gullychain.xyz",
    "explorer": "https://testnet-explorer.gullychain.xyz",
    "txUrlTemplate": "https://testnet-explorer.gullychain.xyz/tx/{hash}",
    "assets": {
      "ETH": { "native": true, "address": null, "decimals": 18 },
      "WETH": { "native": false, "address": "0x26aCCCb2e98ca9e067E58668Aa3B101beF432754", "decimals": 18 }
    }
  }
}
}

Supported networks

Network idChainChain idAssetsNotes
baseBase8453USDC, USDTMainnet
base-sepoliaBase Sepolia84532USDC (test)Testnet
ethereumEthereum1USDC, USDTMainnet
bscBNB Smart Chain56USDC, USDTMainnet
solanaSolananoneUSDC, USDTtxHash is the base58 signature
gully-chain-testnetGully Chain Testnet (Gully Labs L3)485590ETH, WETHOnly while GULLY_CHAIN_ENABLED=true

POST /x402/pay

Submit a payment to receive a receipt token.

Request Body
{
"resource": "https://api.example.com/ai/generate",
"txHash": "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0",
"network": "base",
"amount": "100",
"payerAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38"
}
FieldTypeRequiredDescription
resourcestringYesURL of the resource being paid for
txHashstringYesOn-chain proof of payment. For EVM networks (base, ethereum, bsc, gully-chain-testnet) this is the 0x-prefixed transaction hash; for solana it is the base58 transaction signature.
networkstringYesBlockchain network (base, ethereum, bsc, solana, or gully-chain-testnet when enabled)
amountstringYesPayment amount in the smallest unit
payerAddressstringYesPayer's wallet address
Response
{
"receiptToken": "rcpt_a1b2c3d4e5f6...",
"resource": "https://api.example.com/ai/generate",
"amount": "100",
"currency": "USDC",
"network": "base",
"expiresAt": "2025-06-09T13:00:00Z"
}

POST /x402/verify

Verify that a receipt token is valid before serving a resource.

Request Body
{
"receiptToken": "rcpt_a1b2c3d4e5f6..."
}
Response
{
"valid": true,
"resource": "https://api.example.com/ai/generate",
"amount": "100",
"currency": "USDC",
"payer": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38",
"network": "base",
"verifiedAt": "2025-06-09T12:01:00Z"
}

POST /x402/refund

Executes a real on-chain refund: the platform settlement (hot) wallet returns the paid amount to the original payer's address and the response includes the actual outbound transaction hash (refundTxHash). The receipt is expired immediately so it can no longer be redeemed.

Request Body
{
"receiptToken": "rcpt_a1b2c3d4e5f6...",
"reason": "Service unavailable"
}
Response
{
"status": "refund_executed",
"receiptToken": "rcpt_a1b2c3d4e5f6...",
"amount": "100",
"refundTxHash": "0x9f2c...",
"reason": "Service unavailable"
}

Refunds are idempotent — calling refund again for an already-refunded receipt returns the existing refundTxHash and never sends funds twice.

Settlement hot wallet required

Refunds move real funds and therefore require the platform settlement hot wallet to be configured. The refund settles on the same network as the original payment: EVM networks (Base / Ethereum / BSC) use the secp256k1 hot key (SETTLEMENT_WALLET_ENCRYPTED_KEY + SETTLEMENT_WALLET_ENCRYPTION_PASSWORD); Solana uses its own ed25519 hot key (SOLANA_SETTLEMENT_ENCRYPTED_KEY + SOLANA_SETTLEMENT_ADDRESS + SOLANA_RPC_URL) and returns the base58 transaction signature as refundTxHash. If the hot wallet for that network is not configured — or the on-chain transfer fails — the refund fails closed with 503 Service Unavailable and the payment is not marked refunded. No hash is ever fabricated.


Merchant Payouts

Submit a real on-chain payout from the platform settlement wallet to a merchant's address. Authenticated with the merchant JWT (same as endpoint management).

POST/x402/merchants/:merchantId/payoutsSend an on-chain payout of a caller-specified amount
GET/x402/merchants/:merchantId/owedAutomated owed-balance report per network + asset
POST/x402/merchants/:merchantId/payouts/owedCompute what's owed for a network + asset and settle it on-chain
GET/x402/merchants/:merchantId/payoutsList a merchant's on-chain payouts

POST /x402/merchants/:merchantId/payouts

Request Body
{
"to": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38",
"network": "base",
"asset": "USDC",
"amount": "1000000"
}
FieldTypeRequiredDescription
tostringYesDestination address — EVM (0x…) or Solana (base58), matching network
networkstringYesbase, ethereum, bsc, or solana
assetstringYesUSDC, USDT, or glUSD
amountstringYesInteger amount in the asset's on-chain base units (e.g. 1000000 = 1 USDC at 6 decimals)
Response
{
"id": "b3f1...",
"merchantId": "m_123",
"to": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38",
"network": "base",
"asset": "USDC",
"amount": "1000000",
"txHash": "0x4a7b...",
"createdAt": "2026-07-18T12:00:00Z"
}
Fail-closed & scope

Payouts require the settlement hot wallet for the target network (same env as refunds — EVM secp256k1 key for Base/Ethereum/BSC, ed25519 key for Solana) and fail closed (503) when that network's wallet is unconfigured. POST /payouts is a manual primitive that pays the exact amount specified; for automated settlement use the owed-balance flow below.

Platform fee schedule (per rail)

Every settled payment records a platform fee in the fee ledger, priced per rail so crypto rails are the cheapest option. Amounts are charged in the rail's own minor unit — cents for fiat, the asset's on-chain base units for crypto.

RailRateApplied on
Card2.9% + 30¢Card/Apple Pay/Google Pay capture (payments.confirm)
ACH0.8%, capped at $5Bank debit
Stablecoin1.0%Verified crypto checkout settlement
x4021.0%Verified x402 pay
glUSD0.5%glUSD checkout / glUSD-denominated x402 endpoints

The merchant dashboard Finance → Platform fees panel surfaces these grouped by source. The card fee applies on the fiat capture path; the stablecoin/x402/glUSD fees apply on their crypto settlement paths in the asset's base units.

GET /x402/merchants/:merchantId/owed

Automated owed-balance computation. Returns, per network + asset, the merchant's settlement position with every amount as a decimal string of the asset's on-chain base units (fully precise for 18-decimal assets):

owed = collected − fees − refunded − alreadyPaid   (clamped ≥ 0)

where collected sums verified x402 payments across the merchant's endpoints plus verified crypto checkout transactions, fees sums the crypto fee ledger, refunded sums refunded x402 payments, and alreadyPaid sums prior on-chain payouts.

Response
[
{
  "network": "base",
  "asset": "USDC",
  "collected": "2000000",
  "fees": "20000",
  "refunded": "0",
  "alreadyPaid": "500000",
  "owed": "1480000"
}
]

POST /x402/merchants/:merchantId/payouts/owed

Compute what the merchant is owed for a single network + asset and settle exactly that amount on-chain — no caller-specified amount. Rejects with 400 when nothing is owed and fails closed (503) when the settlement wallet is unconfigured.

Request Body
{
"to": "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38",
"network": "base",
"asset": "USDC"
}
FieldTypeRequiredDescription
tostringYesDestination address — EVM (0x…) or Solana (base58), matching network
networkstringYesbase, ethereum, bsc, or solana
assetstringYesUSDC, USDT, or glUSD

The response matches POST /payouts (a recorded x402_payouts row with the real txHash), where amount is the computed owed base units.


Merchant Endpoint Registration

Register your API endpoints to be monetized via x402.

POST/x402/merchants/:merchantId/endpointsRegister a monetized endpoint
GET/x402/merchants/:merchantId/endpointsList registered endpoints
GET/x402/merchants/:merchantId/usageGet usage analytics and revenue

POST /x402/merchants/:merchantId/endpoints

Request Body
{
"urlPattern": "/api/v1/ai/generate",
"price": "100",
"currency": "USDC",
"scheme": "exact",
"network": "base",
"description": "AI text generation endpoint"
}
FieldTypeRequiredDescription
urlPatternstringYesURL pattern to monetize (supports wildcards)
pricestringYesPrice per request in the asset's on-chain base units, as a decimal integer string (min "1", up to 78 digits). A JSON integer is accepted for compatibility, but 18-decimal prices (ETH, BSC stablecoins) exceed JSON number precision: always send a string.
currencystringNoUSDC, USDT, or glUSD (default: USDC)
schemestringNoexact, metered, or subscription (default: exact)
networkstringNoPreferred blockchain network (default: base)
descriptionstringNoHuman-readable description of the endpoint

TypeScript SDK

x402-client.ts
1import { GullyPayClient } from '@gullypay/x402-sdk';
2
3const client = new GullyPayClient({
4facilitatorUrl: 'https://gullypay.xyz/api',
5apiKey: 'rk_live_your_api_key',
6});
7
8// Automatic 402 handling -- the SDK detects 402 responses,
9// pays via the facilitator, and retries the request
10const response = await client.fetchWithPayment(
11'https://api.example.com/ai/generate',
12async (requirements) => {
13  return client.pay({
14    resource: requirements.resourceUrl,
15    txHash: '0x...',
16    network: requirements.network,
17    amount: requirements.price,
18    payerAddress: '0x742d35Cc...',
19  });
20},
21);
22
23const data = await response.json();
24console.log(data);

Python SDK

x402_client.py
1from gullypay import GullyPayClient
2
3client = GullyPayClient(
4  facilitator_url="https://gullypay.xyz/api",
5  api_key="rk_live_your_api_key",
6)
7
8# Pay for a resource
9receipt = client.pay(
10  resource="https://api.example.com/ai/generate",
11  tx_hash="0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0",
12  network="base",
13  amount="100",
14  payer_address="0x742d35Cc6634C0532925a3b844Bc9e7595f2bD38",
15)
16
17# Use the receipt to access the resource
18import requests
19
20response = requests.get(
21  "https://api.example.com/ai/generate",
22  headers={"X-Payment-Receipt": receipt.token},
23)
24
25print(response.json())

Server-Side Integration

Here is how to protect your API endpoints with x402 on the server side.

Express.js Middleware

x402-middleware.ts
1import express from 'express';
2
3const FACILITATOR_URL = 'https://gullypay.xyz/api';
4
5function requirePayment(price: number, currency = 'USDC', network = 'base') {
6return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
7  const receipt = req.headers['x-payment-receipt'] as string;
8
9  if (!receipt) {
10    // Return 402 with payment requirements
11    const requirements = {
12      price,
13      currency,
14      network,
15      facilitatorUrl: FACILITATOR_URL,
16      resourceUrl: `${req.protocol}://${req.get('host')}${req.originalUrl}`,
17    };
18
19    res.status(402)
20      .header('X-Payment', JSON.stringify(requirements))
21      .json(requirements);
22    return;
23  }
24
25  // Verify the receipt with the facilitator
26  const verification = await fetch(`${FACILITATOR_URL}/x402/verify`, {
27    method: 'POST',
28    headers: { 'Content-Type': 'application/json' },
29    body: JSON.stringify({ receiptToken: receipt }),
30  });
31
32  const result = await verification.json();
33
34  if (!result.valid) {
35    res.status(402).json({ error: 'Invalid payment receipt' });
36    return;
37  }
38
39  next();
40};
41}
42
43const app = express();
44
45// Protect an endpoint with x402 -- $0.01 per request
46app.get('/api/v1/ai/generate', requirePayment(100, 'USDC', 'base'), (req, res) => {
47res.json({ result: 'Your AI-generated content here' });
48});

Pricing Models

ModelDescriptionUse Case
ExactFixed price per requestSimple API calls, data lookups
MeteredPrice based on usage (tokens, bytes, compute time)AI inference, streaming, large queries
SubscriptionRecurring access with usage limitsSaaS APIs, data feeds, premium tiers

Metered Pricing Example

For metered endpoints, the final cost is determined after the request completes:

Metered Requirements (402 Response)
{
"scheme": "metered",
"basePricePerUnit": 1,
"unit": "token",
"currency": "USDC",
"network": "base",
"estimatedUnits": 500,
"maxPrice": 1000,
"facilitatorUrl": "https://gullypay.xyz/api"
}

Supported Networks

NetworkChain IDSettlement SpeedRecommended For
Base8453~2 secondsPrimary -- lowest fees, fastest settlement
Ethereum1~12 secondsHigh-value settlements
BSC56~3 secondsHigh-volume, low-fee EVM settlements
Solana--~0.4 secondsHigh-throughput applications
Recommended Network

We recommend Base for most x402 integrations. It offers the lowest transaction fees and fastest settlement times while maintaining Ethereum-level security through optimistic rollups.

On-chain settlement assets

Payments are settled in SPL/ERC-20 stablecoins. The facilitator verifies that the submitted transaction credited the merchant's settlement address with at least the required amount of the expected token before issuing a receipt.

NetworkPayment proof (txHash)USDCUSDT
Base0x transaction hash0x8335...029130xfde4...99bb2
Ethereum0x transaction hash0xa0b8...6eb480xdac1...31ec7
BSC0x transaction hash0x8ac7...d580d0x5533...97955
Solanabase58 transaction signatureEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1vEs9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB
Amounts are strings

Every on-chain amount the facilitator stores or returns (price, payment amount, totalRevenue, timeseries revenue, fees, owed balances) is a decimal string of base units, exact up to 78 digits (uint256). Parse with BigInt, never Number.

BSC token decimals

On BSC, both USDC and USDT use 18 decimals (unlike the 6-decimal USDC on Base/Ethereum/Solana). Since an endpoint's price is denominated in the token's base units, price BSC endpoints accordingly (e.g. 0.02 USDT = 20000000000000000).

Solana settlement

On Solana, submit the base58 transaction signature as txHash. For inbound verification the facilitator fetches the finalized transaction and confirms an SPL-token credit to the settlement wallet's associated token account by diffing pre/post token balances. Outbound settlement (refunds + payouts) is also supported on Solana: it signs an SPL transferChecked from the platform's ed25519 Solana hot wallet (creating the recipient's associated token account if needed) and returns the real base58 signature. glUSD on Solana uses its configured mint for both.

Next Steps