Skip to main content

What is x402?

x402 is a protocol for HTTP payment-required responses that enables:
  • Machine-readable payment challenges using HTTP 402 status
  • Automatic payment settlement without custom billing logic
  • Retry mechanisms after successful payment
  • Transparent pricing embedded in API responses
ActumX implements a simplified x402 flow. The protocol is inspired by HTTP 402 Payment Required and designed for AI agent interactions.

The x402 Flow

1

Make Initial Request

Request a paid endpoint without payment proof:
curl -X GET "http://localhost:3001/v1/protected/quote?topic=ai" \
  -H "Authorization: Bearer xk_live_..."
Response (402 Payment Required):
{
  "error": "payment_required",
  "message": "This endpoint requires payment. Settle first and retry with payment proof.",
  "x402": {
    "version": "0.1-draft",
    "paymentId": "x402tx_abc123def456",
    "amountCents": 25,
    "amountUsd": "0.25",
    "currency": "USD",
    "endpoint": "/v1/protected/quote",
    "settlementEndpoint": "/v1/x402/settle",
    "facilitator": "internal-simulator",
    "expiresAt": "2026-03-03T23:40:00.000Z"
  }
}
The system automatically creates a pending transaction in the database with a unique paymentId.
2

Settle the Payment

Use the paymentId to settle the payment challenge:
curl -X POST http://localhost:3001/v1/x402/settle \
  -H "Authorization: Bearer xk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "paymentId": "x402tx_abc123def456"
  }'
Response (200 Success):
{
  "receiptId": "receipt_xyz789ghi012",
  "paymentId": "x402tx_abc123def456",
  "status": "settled",
  "amountCents": 25,
  "settledAt": "2026-03-03T23:35:00.000Z"
}
The settlement deducts 25 cents from your account balance and generates a receiptId as proof of payment.
3

Retry with Payment Proof

Make the original request again, this time including payment proof headers:
curl -X GET "http://localhost:3001/v1/protected/quote?topic=ai" \
  -H "Authorization: Bearer xk_live_..." \
  -H "X-Payment-Id: x402tx_abc123def456" \
  -H "X-Payment-Proof: receipt_xyz789ghi012"
Response (200 Success):
{
  "data": {
    "topic": "ai",
    "insight": "x402 allows machine-readable payment requirements using HTTP 402 so clients can settle and retry without custom per-API billing logic.",
    "generatedAt": "2026-03-03T23:36:00.000Z"
  },
  "payment": {
    "paymentId": "x402tx_abc123def456",
    "receiptId": "receipt_xyz789ghi012",
    "amountCents": 25,
    "status": "completed"
  }
}
Success! The transaction is marked as completed and a usage event is recorded.

Complete Example: Automated Flow

Here’s a complete example that handles the entire x402 flow automatically:
async function makeX402Request(endpoint, apiKey, queryParams = {}) {
  const url = new URL(endpoint);
  Object.entries(queryParams).forEach(([key, value]) => {
    url.searchParams.append(key, value);
  });
  
  // Step 1: Initial request
  let response = await fetch(url, {
    headers: {
      'Authorization': `Bearer ${apiKey}`
    }
  });
  
  let data = await response.json();
  
  // Step 2: Handle payment required
  if (response.status === 402 && data.x402) {
    console.log(`💰 Payment required: $${data.x402.amountUsd}`);
    
    // Settle the payment
    const settleResponse = await fetch('http://localhost:3001/v1/x402/settle', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        paymentId: data.x402.paymentId
      })
    });
    
    const settlement = await settleResponse.json();
    
    if (settleResponse.status !== 200) {
      throw new Error(`Settlement failed: ${JSON.stringify(settlement)}`);
    }
    
    console.log(`✅ Payment settled: ${settlement.receiptId}`);
    
    // Step 3: Retry with proof
    response = await fetch(url, {
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'X-Payment-Id': settlement.paymentId,
        'X-Payment-Proof': settlement.receiptId
      }
    });
    
    data = await response.json();
  }
  
  return data;
}

// Usage
const result = await makeX402Request(
  'http://localhost:3001/v1/protected/quote',
  process.env.ACTUMX_API_KEY,
  { topic: 'ai' }
);

console.log(result);

Payment States

An x402 transaction goes through these states:
1

Pending

Initial state when payment challenge is issued
  • Transaction created in database
  • paymentId generated
  • User has not settled yet
2

Settled

Payment has been deducted from user’s balance
  • Credits deducted from account
  • receiptId generated
  • Ready for endpoint access
3

Completed

User successfully accessed the endpoint with proof
  • Usage event recorded
  • API key’s lastUsedAt updated
  • Transaction finalized

Implementation Details

Creating Payment Challenge

From api/src/modules/x402/service.ts:352-375:
if (!paymentId || !paymentProof) {
  const txId = newId("x402tx");
  const timestamp = TimeService.nowIso();

  await db.insert(x402Transactions).values({
    id: txId,
    userId: apiKey.userId,
    apiKeyId: apiKey.id,
    endpoint: X402_PAID_ENDPOINT,
    method: "GET",
    amountCents: X402_PAID_REQUEST_COST_CENTS,
    status: "pending",
    // ...
  });

  return {
    statusCode: 402,
    body: X402Service.buildPaymentRequiredResponse(txId),
  };
}

Settling Payment

From api/src/modules/x402/service.ts:307-324:
await db.insert(creditLedger).values({
  id: newId("ledger"),
  userId: apiKey.userId,
  direction: "debit",
  amountCents: transaction.amountCents,
  source: "api_request",
  referenceId: transaction.id,
  createdAt: timestamp,
});

await db
  .update(x402Transactions)
  .set({
    status: "settled",
    receiptId,
    updatedAt: timestamp,
  })
  .where(eq(x402Transactions.id, transaction.id));

Error Handling

Error Response:
{
  "error": "insufficient_balance",
  "requiredCents": 25,
  "balanceCents": 10,
  "message": "Top up balance in dashboard before settling this x402 payment."
}
Solution: Top up your account balance before settling.
Error Response:
{
  "error": "invalid_payment_proof"
}
Causes:
  • paymentId doesn’t exist
  • receiptId doesn’t match the transaction
  • Payment belongs to a different user/API key
Solution: Verify you’re using the correct IDs from the settlement response.
Error Response:
{
  "error": "payment_not_settled",
  "status": "pending"
}
Solution: Complete the settlement step before retrying the endpoint.
Error Response:
{
  "error": "payment_not_found"
}
Causes:
  • Invalid paymentId
  • Payment belongs to different user
  • Payment was already consumed and expired

Best Practices

Automate the Flow

Build helper functions that handle the 3-step flow automatically

Cache Receipt IDs

Store receipt IDs to avoid duplicate charges for the same request

Handle Retries

Implement exponential backoff for network errors during settlement

Monitor Balance

Check your balance before making paid requests to avoid 402 errors

Available Paid Endpoints

Quote Endpoint

Endpoint: GET /v1/protected/quoteCost: 25 cents per requestQuery Parameters:
  • topic (optional): Topic for the quote (default: “general”)
Example:
curl "http://localhost:3001/v1/protected/quote?topic=blockchain" \
  -H "Authorization: Bearer xk_live_..."
More paid endpoints will be added. Each will follow the same x402 protocol.

Next Steps