WS402 extends the X402 protocol to WebSocket connections, enabling true pay-as-you-go pricing for streaming resources. Users pay upfront for estimated usage, the backend tracks actual consumption in real-time, and unused balance is automatically refunded when the session ends.
ws402 is an early-stage TypeScript project in the AI payments / x402 ecosystem, focused on agents, base, ia, x402. It currently has 5 GitHub stars and 1 forks, and sits alongside related tools like pinion-os, x402-deploy, Agent-Layer, pinion-os, mcp-client, daydreams.
WebSocket implementation of X402 protocol for pay-as-you-go digital resources with automatic refunds
WS402 extends the X402 protocol to WebSocket connections, enabling true pay-as-you-go pricing for streaming resources. Users pay upfront for estimated usage, the backend tracks actual consumption in real-time, and unused balance is automatically refunded when the session ends.
Perfect for:
npm install ws402
Additional dependencies for blockchain providers:
# For Base blockchain
npm install ethers
# For Solana blockchain
npm install @solana/web3.js @solana/pay bignumber.js
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const { WS402, MockPaymentProvider } = require('ws402');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
// Initialize payment provider (mock for development)
const paymentProvider = new MockPaymentProvider();
// Create WS402 instance
const ws402 = new WS402(
{
updateInterval: 3000, // Send updates every 3 seconds
pricePerSecond: 10, // Price per second in wei
currency: 'wei',
maxSessionDuration: 600, // Max 10 minutes per session
onPaymentVerified: (session) => {
console.log(`✅ Payment verified: ${session.sessionId}`);
},
onRefundIssued: (session, refund) => {
console.log(`💰 Refund issued: ${refund.amount} wei`);
},
onSessionEnd: (session) => {
console.log(`🔚 Session ended: ${session.sessionId}`);
},
},
paymentProvider
);
// Attach to WebSocket server
ws402.attach(wss);
// Serve WS402 schema
app.get('/ws402/schema/:resourceId', (req, res) => {
const schema = ws402.generateSchema(
req.params.resourceId,
300 // estimated duration in seconds
);
res.json(schema);
});
server.listen(4028);
// 1. Fetch WS402 schema
const response = await fetch('/ws402/schema/my-resource?duration=300');
const schema = await response.json();
// 2. Make payment (using blockchain or other method)
// ... payment process ...
// 3. Connect to WebSocket
const ws = new WebSocket(`wss://your-server.com?userId=alice`);
// 4. Send payment proof
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'payment_proof',
proof: {
txHash: '0x123...', // Blockchain transaction hash
reference: schema.paymentDetails.reference,
senderAddress: '0xYourAddress...',
amount: schema.pricing.totalPrice,
}
}));
};
// 5. Receive usage updates
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'session_started') {
console.log('✅ Session active!', data.sessionId);
}
if (data.type === 'usage_update') {
console.log(`⏱️ Time: ${data.elapsedSeconds}s`);
console.log(`💰 Remaining: ${data.remainingBalance} wei`);
}
if (data.type === 'balance_exhausted') {
console.log('⚠️ Balance exhausted');
}
};
// 6. Disconnect to trigger automatic refund
ws.close();
WS402 supports multiple payment architectures:
const { WS402, BasePaymentProvider } = require('ws402');
const baseProvider = new BasePaymentProvider({
rpcEndpoint: 'https://mainnet.base.org',
merchantWallet: '0xYourWalletAddress',
merchantPrivateKey: process.env.MERCHANT_PRIVATE_KEY, // For automatic refunds
network: 'base', // 'base' | 'base-goerli' | 'base-sepolia'
autoRefund: true,
});
const ws402 = new WS402(config, baseProvider);
Features:
The Solana Payment Provider enables ultra-fast, low-cost payments on the Solana blockchain with automatic refunds.
WebSocket Real-Time Streaming

HTTP Resource Protection

const { WS402, SolanaPaymentProvider } = require('ws402');
const solanaProvider = new SolanaPaymentProvider({
rpcEndpoint: 'https://api.mainnet-beta.solana.com',
merchantWallet: 'YourSolanaPublicKey',
merchantPrivateKey: [1, 2, 3, ...], // Array from wallet JSON
network: 'mainnet-beta',
conversionRate: 1, // 1:1 lamports
label: 'WS402 Payment',
message: 'Pay for WebSocket access',
autoRefund: true,
});
const ws402 = new WS402(config, solanaProvider);
interface SolanaPaymentProviderConfig {
// Required
rpcEndpoint: string; // Solana RPC endpoint
merchantWallet: string; // Your wallet address (base58)
// Optional - for automatic refunds
merchantPrivateKey?: number[]; // Wallet private key as array
// Optional settings
network?: 'mainnet-beta' | 'devnet' | 'testnet';
conversionRate?: number; // wei/lamports conversion (default: 1)
splToken?: string; // SPL token mint address
paymentTimeout?: number; // Payment timeout in ms (default: 300000)
label?: string; // Solana Pay QR label
message?: string; // Solana Pay message
memo?: string; // Transaction memo
autoRefund?: boolean; // Enable auto-refunds (default: true if privateKey exists)
}
Your Solana private key is required for automatic refunds. Here's how to get it:
From Phantom/Solflare Wallet:
# Export private key from wallet settings
# It will be in base58 format like: 4p8Qsp4esg...
MERCHANT_PRIVATE_KEY=4p8Qsp4esg...
From Solana CLI:
# Generate new keypair
solana-keygen new -o my-wallet.json
# View the keypair as array
cat my-wallet.json
# Output: [1,2,3,4,...,64]
In your code:
// From base58 string (Phantom export)
const privateKey = bs58.decode('4p8Qsp4esg...');
// Or from JSON array
const privateKey = [1, 2, 3, ..., 64];
// Use in provider
const solanaProvider = new SolanaPaymentProvider({
// ...other config
merchantPrivateKey: Array.from(privateKey),
});
Perfect for real-time data streams where users pay for connection time:

const ws402 = new WS402(
{
updateInterval: 3000, // Update every 3 seconds
pricePerSecond: 100000, // 0.0001 SOL per second
currency: 'lamports',
maxSessionDuration: 3600, // 1 hour max
onPaymentVerified: (session) => {
console.log(`✅ Payment verified: ${session.sessionId}`);
console.log(` Amount: ${session.paidAmount / 1e9} SOL`);
},
onRefundIssued: (session, refund) => {
console.log(`💰 Refund: ${refund.amount / 1e9} SOL`);
},
},
solanaProvider
);
Protect HTTP resources (PDFs, videos, images) with time-based billing via WebSocket:

const { WS402HTTPMiddleware } = require('ws402');
// Initialize HTTP middleware
const httpMiddleware = new WS402HTTPMiddleware(ws402);
// Different resources can have different prices
const RESOURCES = {
'premium-report': {
type: 'pdf',
path: './reports/premium.pdf',
pricePerSecond: 500000, // 0.0005 SOL/sec
estimatedTime: 600, // 10 minutes
},
'basic-guide': {
type: 'pdf',
path: './guides/basic.pdf',
pricePerSecond: 100000, // 0.0001 SOL/sec
estimatedTime: 300, // 5 minutes
},
};
// Schema endpoint with resource-specific pricing
app.get('/api/resource/:resourceId/schema', (req, res) => {
const resource = RESOURCES[req.params.resourceId];
const schema = ws402.generateSchema(
req.params.resourceId,
resource.estimatedTime,
resource.pricePerSecond // Custom price per resource
);
res.json({ ws402Schema: schema });
});
// Protected resource endpoint
app.get('/api/resource/:resourceId',
httpMiddleware.protectHTTPResource(),
(req, res) => {
const resource = RESOURCES[req.params.resourceId];
res.sendFile(resource.path);
}
);
// Register HTTP session after WS402 payment
ws402.on('payment_verified', (session) => {
const token = httpMiddleware.registerHTTPSession(
session.sessionId,
session._resourceId
);
// Send token to client for HTTP access
clientWs.send(JSON.stringify({
type: 'http_access_granted',
httpToken: token,
resourceUrl: `/api/resource/${session._resourceId}?token=${token}`,
}));
});
How it works:
┌─────────┐ ┌─────────┐ ┌──────────┐
│ Client │ │ WS402 │ │ Solana │
│ │ │ Server │ │ Chain │
└────┬────┘ └────┬────┘ └────┬─────┘
│ │ │
│ 1. Request Schema │ │
│────────────────────────────>│ │
│ │ │
│ 2. Schema + Payment Details │ │
│<────────────────────────────│ │
│ (QR code, address, amount) │ │
│ │ │
│ 3. Sign & Send Transaction │ │
│──────────────────────────────────────────────────────────>│
│ │ │
│ │ 4. Verify Transaction │
│ │<─────────────────────────────│
│ │ │
│ 5. Connect WebSocket │ │
│────────────────────────────>│ │
│ │ │
│ 6. Send Payment Proof │ │
│ (signature + reference) │ │
│────────────────────────────>│ │
│ │ │
│ │ 7. Verify On-Chain │
│ │────────────────────────────>│
│ │ │
│ │ 8. Confirmation │
│ │<────────────────────────────│
│ │ │
│ 9. Session Started │ │
│<────────────────────────────│ │
│ │ │
│ 10. Usage Updates (periodic) │ │
│<────────────────────────────│ │
│ │ │
│ 11. Disconnect │ │
│────────────────────────────>│ │
│ │ │
│ │ 12. Send Refund (if any) │
│ │────────────────────────────>│
│ │ │
│ 13. Session Ended │ 14. Refund Confirmed │
│<────────────────────────────│<────────────────────────────│
│ │ │
When you call generateSchema(), the Solana provider returns:
{
"protocol": "ws402",
"version": "0.1.2",
"resourceId": "video-stream-123",
"websocketEndpoint": "wss://api.example.com/ws402",
"pricing": {
"pricePerSecond": 100000,
"currency": "lamports",
"estimatedDuration": 300,
"totalPrice": 30000000
},
"paymentDetails": {
"type": "solana",
"network": "mainnet-beta",
"recipient": "YourWalletPublicKey",
"amount": 30000000,
"amountSOL": "0.03",
"currency": "SOL",
"reference": "UniqueReferencePublicKey",
"solanaPayURL": "solana:YourWallet?amount=0.03&reference=...",
"qrCode": "solana:...",
"expiresAt": 1234567890000,
"instructions": {
"step1": "Scan QR code with Solana-compatible wallet",
"step2": "Or use Phantom, Solflare, or other Solana wallet",
"step3": "Approve transaction",
"step4": "Connection will be established automatically"
}
},
"maxSessionDuration": 3600
}
Web App with Phantom Wallet:
// 1. Get schema
const response = await fetch('/ws402/schema/my-resource?duration=300');
const schema = await response.json();
// 2. Connect to Phantom wallet
if (!window.solana || !window.solana.isPhantom) {
alert('Please install Phantom wallet');
return;
}
await window.solana.connect();
const provider = window.solana;
// 3. Create payment transaction
const { Connection, PublicKey, Transaction, SystemProgram } = window.solanaWeb3;
const connection = new Connection('https://api.mainnet-beta.solana.com');
const { blockhash } = await connection.getLatestBlockhash();
const transaction = new Transaction({
feePayer: provider.publicKey,
recentBlockhash: blockhash,
});
// Add transfer instruction
transaction.add(
SystemProgram.transfer({
fromPubkey: provider.publicKey,
toPubkey: new PublicKey(schema.paymentDetails.recipient),
lamports: schema.paymentDetails.amount,
})
);
// Add reference as read-only account (for tracking)
transaction.add({
keys: [{
pubkey: new PublicKey(schema.paymentDetails.reference),
isSigner: false,
isWritable: false
}],
programId: SystemProgram.programId,
data: Buffer.alloc(0),
});
// 4. Sign and send transaction
const { signature } = await provider.signAndSendTransaction(transaction);
console.log('Payment sent:', signature);
// 5. Connect to WebSocket
const ws = new WebSocket(schema.websocketEndpoint);
ws.onopen = () => {
// Send payment proof
ws.send(JSON.stringify({
type: 'payment_proof',
proof: {
signature: signature,
reference: schema.paymentDetails.reference,
senderAddress: provider.publicKey.toBase58(),
amount: schema.paymentDetails.amount,
}
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'session_started') {
console.log('✅ Session started!');
console.log('Balance:', data.balance / 1e9, 'SOL');
}
if (data.type === 'usage_update') {
console.log('Time:', data.elapsedSeconds, 's');
console.log('Remaining:', data.remainingBalance / 1e9, 'SOL');
}
};
// 6. Close to trigger refund
ws.close();
React Example:
import { useState } from 'react';
import { useWallet } from '@solana/wallet-adapter-react';
import { Connection, Transaction, SystemProgram, PublicKey } from '@solana/web3.js';
function PayPerUseStream() {
const { publicKey, signTransaction } = useWallet();
const [session, setSession] = useState(null);
const [ws, setWs] = useState(null);
const startSession = async () => {
// 1. Fetch schema
const res = await fetch('/ws402/schema/stream?duration=600');
const schema = await res.json();
// 2. Create payment
const connection = new Connection('https://api.mainnet-beta.solana.com');
const { blockhash } = await connection.getLatestBlockhash();
const tx = new Transaction({
feePayer: publicKey,
recentBlockhash: blockhash
});
tx.add(
SystemProgram.transfer({
fromPubkey: publicKey,
toPubkey: new PublicKey(schema.paymentDetails.recipient),
lamports: schema.paymentDetails.amount,
})
);
const signed = await signTransaction(tx);
const signature = await connection.sendRawTransaction(signed.serialize());
// 3. Connect WebSocket
const websocket = new WebSocket(schema.websocketEndpoint);
websocket.onopen = () => {
websocket.send(JSON.stringify({
type: 'payment_proof',
proof: {
signature,
reference: schema.paymentDetails.reference,
senderAddress: publicKey.toBase58(),
amount: schema.paymentDetails.amount,
}
}));
};
websocket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'session_started') {
setSession(data);
}
if (data.type === 'usage_update') {
setSession(prev => ({ ...prev, ...data }));
}
};
setWs(websocket);
};
const endSession = () => {
if (ws) {
ws.close(); // Triggers automatic refund
}
};
return (
<div>
<button onClick={startSession}>Start Session</button>
{session && (
<div>
<p>Time: {session.elapsedSeconds}s</p>
<p>Remaining: {session.remainingBalance / 1e9} SOL</p>
<button onClick={endSession}>End & Refund</button>
</div>
)}
</div>
);
}
# Solana Configuration
SOLANA_RPC=https://api.mainnet-beta.solana.com
SOLANA_NETWORK=mainnet-beta
# Merchant Wallet (public key)
MERCHANT_WALLET=YourSolanaPublicKeyHere
# Merchant Private Key (for automatic refunds)
# Option 1: JSON array from wallet file
MERCHANT_PRIVATE_KEY='[1,2,3,4,...,64]'
# Option 2: Base58 string from Phantom/Solflare
# MERCHANT_PRIVATE_KEY=4p8Qsp4esg...
# Optional: SPL Token (for USDC, USDT, etc.)
# SPL_TOKEN_MINT=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
# Server
PORT=4028
# Use devnet for testing
SOLANA_RPC=https://api.devnet.solana.com
SOLANA_NETWORK=devnet
# Get free devnet SOL
# Visit: https://faucet.solana.com
# Or use CLI: solana airdrop 2 YourWalletAddress --url devnet
const solanaProvider = new SolanaPaymentProvider({
rpcEndpoint: 'https://api.mainnet-beta.solana.com',
merchantWallet: 'YourSolanaPublicKey',
merchantPrivateKey: privateKeyArray,
splToken: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC mint
network: 'mainnet-beta',
conversionRate: 1000000, // 1 USDC = 1,000,000 units (6 decimals)
});
1. Private Key Management:
// ❌ NEVER hardcode private keys
const provider = new SolanaPaymentProvider({
merchantPrivateKey: [1, 2, 3, ...],
});
// ✅ ALWAYS use environment variables
const provider = new SolanaPaymentProvider({
merchantPrivateKey: process.env.MERCHANT_PRIVATE_KEY
? JSON.parse(process.env.MERCHANT_PRIVATE_KEY)
: undefined,
});
2. Network Validation:
// Verify you're on the correct network
const info = solanaProvider.getConnectionInfo();
console.log('Network:', info.network);
console.log('Auto-refund:', info.autoRefundEnabled);
if (info.network === 'devnet' && process.env.NODE_ENV === 'production') {
throw new Error('Cannot use devnet in production!');
}
3. Balance Monitoring:
// Monitor merchant wallet balance
const { Connection, LAMPORTS_PER_SOL } = require('@solana/web3.js');
const connection = new Connection(process.env.SOLANA_RPC);
const balance = await connection.getBalance(
new PublicKey(process.env.MERCHANT_WALLET)
);
console.log('Merchant balance:', balance / LAMPORTS_PER_SOL, 'SOL');
// Alert if balance is low
if (balance < 0.1 * LAMPORTS_PER_SOL) {
console.warn('⚠️ Low balance! May affect refunds.');
}
4. Error Handling:
ws402.on('refund_error', ({ session, error }) => {
console.error('Refund failed:', error);
// Log to monitoring service
monitoringService.logError({
type: 'refund_failed',
sessionId: session.sessionId,
userId: session.userId,
amount: session.paidAmount - session.consumedAmount,
error: error.message,
});
// Queue for manual processing
refundQueue.add({
senderWallet: session.paymentProof.senderAddress,
amount: session.paidAmount - session.consumedAmount,
reason: 'automatic_refund_failed',
});
});
Payment Verification with Retries:
The Solana provider automatically retries payment verification:
// Automatic retry logic (built-in):
// - Max 10 retries
// - 2 second delay between retries
// - Handles network delays and finalization
// Transaction typically confirms in < 1 second
// Provider waits up to 20 seconds for confirmation
Refund Confirmation Tracking:
ws402.on('refund', ({ session, refund }) => {
console.log('💰 Refund processing...');
console.log(' Amount:', refund.amount / 1e9, 'SOL');
console.log(' Session:', session.sessionId);
// Check on Solscan
const explorerUrl = `https://solscan.io/tx/${refund.signature}`;
console.log(' Explorer:', explorerUrl);
});
Cleanup Expired Payments:
// Run cleanup periodically
setInterval(() => {
const cleaned = solanaProvider.cleanupExpiredPayments();
if (cleaned > 0) {
console.log(`🧹 Cleaned ${cleaned} expired payments`);
}
}, 60000); // Every minute
Health Check Endpoint:
app.get('/health', async (req, res) => {
const info = solanaProvider.getConnectionInfo();
const connection = new Connection(info.rpcEndpoint);
try {
// Check RPC connection
const blockHeight = await connection.getBlockHeight();
// Check merchant balance
const balance = await connection.getBalance(
new PublicKey(info.merchantWallet)
);
res.json({
status: 'ok',
network: info.network,
blockHeight,
merchantBalance: balance / LAMPORTS_PER_SOL,
autoRefund: info.autoRefundEnabled,
activeSessions: ws402.getActiveSessions().length,
});
} catch (error) {
res.status(503).json({
status: 'error',
error: error.message,
});
}
});
Debug Endpoints:
// Get pending payment info
app.get('/debug/pending/:reference', (req, res) => {
const pending = solanaProvider.getPendingPayment(req.params.reference);
if (!pending) {
return res.status(404).json({ error: 'Payment not found' });
}
res.json({
reference: req.params.reference,
amount: pending.amount,
amountSOL: pending.amountSOL.toString(),
recipient: pending.recipient.toBase58(),
timestamp: new Date(pending.timestamp).toISOString(),
expiresAt: new Date(pending.timestamp + 300000).toISOString(),
});
});
| Feature | Solana | Base |
|---|---|---|
| Transaction Fee | ~$0.0001 | ~$0.01 |
| Confirmation Time | < 1 sec | 2-5 sec |
| Finality | ~13 sec | ~1 min |
| Native Currency | SOL | ETH |
| Token Standard | SPL | ERC20 |
| QR Code Support | ✅ Solana Pay | ❌ |
| Mobile Wallets | Phantom, Solflare, Backpack | MetaMask, Coinbase |
| Best For | High-volume, low-value | EVM ecosystem |
Perfect for scaling and security:
const { WS402, ProxyPaymentProvider } = require('ws402');
// WS402 Server (no private keys needed!)
const proxyProvider = new ProxyPaymentProvider({
gatewayUrl: 'https://payment-gateway.example.com',
apiKey: process.env.GATEWAY_API_KEY,
timeout: 30000,
retryAttempts: 3,
});
const ws402 = new WS402(config, proxyProvider);
Architecture:
[Client] → [WS402 Server A] ⎤
[Client] → [WS402 Server B] ⎥→ [Payment Gateway] → [Blockchain]
[Client] → [WS402 Server C] ⎦ (has private keys)
Benefits:
Gateway Server:
// Centralized gateway with private keys
node examples/payment-gateway-server.js
See: PROXY_ARCHITECTURE.md for details
Implement the PaymentProvider interface:
interface PaymentProvider {
generatePaymentDetails(amount: number): any;
verifyPayment(proof: any): Promise<PaymentVerification>;
issueRefund(proof: any, amount: number): Promise<void>;
}
Example:
class MyPaymentProvider {
async generatePaymentDetails(amount) {
return {
invoiceUrl: 'https://pay.me/invoice-123',
amount,
currency: 'USD',
};
}
async verifyPayment(proof) {
const isValid = await this.checkWithAPI(proof);
return {
valid: isValid,
amount: proof.amount,
};
}
async issueRefund(proof, amount) {
await this.refundAPI(proof, amount);
}
}
interface WS402Config {
updateInterval?: number; // Update frequency (ms) - default: 3000
pricePerSecond?: number; // Price per second - default: 1
currency?: string; // Currency unit - default: 'wei'
maxSessionDuration?: number; // Max session time (seconds) - default: 3600
userIdExtractor?: (req) => string; // Extract user ID from request
onPaymentVerified?: (session) => void;
onRefundIssued?: (session, refund) => void;
onSessionEnd?: (session) => void;
}
When a client requests a protected resource, return a WS402 schema:
{
"protocol": "ws402",
"version": "0.1.0",
"resourceId": "video-123",
"websocketEndpoint": "wss://api.example.com/ws402/video-123",
"pricing": {
"pricePerSecond": 10,
"currency": "wei",
"estimatedDuration": 300,
"totalPrice": 3000
},
"paymentDetails": {
"type": "base",
"network": "base",
"chainId": 8453,
"recipient": "0x...",
"amountETH": "0.000003",
"reference": "base_123_abc"
},
"maxSessionDuration": 600
}
Payment Proof
{
"type": "payment_proof",
"proof": {
"txHash": "0x123...",
"reference": "base_123_abc",
"senderAddress": "0xUser...",
"amount": 3000
}
}
Session Started
{
"type": "session_started",
"sessionId": "ws402_123_abc",
"balance": 3000,
"pricePerSecond": 10
}
Usage Update
{
"type": "usage_update",
"sessionId": "ws402_123_abc",
"elapsedSeconds": 45,
"consumedAmount": 450,
"remainingBalance": 2550,
"bytesTransferred": 1024000,
"messageCount": 15
}
Balance Exhausted
{
"type": "balance_exhausted",
"message": "Prepaid balance has been fully consumed"
}
Payment Rejected
{
"type": "payment_rejected",
"reason": "Invalid payment proof"
}
See the /examples directory for complete working examples:
basic-server.js - Mock payment provider for testingbase-server.js - Base blockchain integrationsolana-server.js - Solana blockchain integration (WebSocket)solana-http-tracker.js - Solana with HTTP resource protectionproxy-server.js - Proxy architecture with gatewaypayment-gateway-server.js - Centralized payment gatewaybase-client.html - Web client with MetaMask integrationsolana-client.html - Web client with Solana Paysolana-http-tracker-client.html - Web client for HTTP resource protectionproxy-client.html - Web client for proxy architecture# Development with mock payments
npm run example
# Base blockchain
npm run example:base
# Solana blockchain (WebSocket)
npm run example:solana
# Solana with HTTP resources
node examples/solana-http-tracker.js
# Proxy architecture (run gateway first)
node examples/payment-gateway-server.js # Terminal 1
node examples/proxy-server.js # Terminal 2
Then open http://localhost:4028 in your browser.
1. Client requests resource → Server returns WS402 schema
2. Client makes payment → Blockchain transaction
3. Client connects via WebSocket → Sends payment proof
4. Server verifies payment on-chain → Starts session
5. Real-time metering → Server sends periodic updates
6. Client disconnects → Server calculates consumed amount
7. Automatic refund → Unused balance returned on-chain
| Feature | X402 | WS402 |
|---|---|---|
| Protocol | HTTP | WebSocket |
| Payment timing | Pay-per-request | Pay upfront, refund unused |
| Connection | Stateless | Stateful |
| Real-time updates | No | Yes |
| Refunds | No | Automatic |
| Use case | API endpoints | Streaming resources |
| Provider | Fees | Speed | Best For |
|---|---|---|---|
| Base | ~$0.01 | 2-5 sec | Ethereum users, DeFi apps |
| Solana | ~$0.0001 | <1 sec | High volume, low fees |
| Proxy | Provider dependent | Provider dependent | Enterprise, multi-server |
| Mock | Free | Instant | Development, testing |
WS402Main class for WS402 protocol implementation.
new WS402(config: WS402Config, paymentProvider: PaymentProvider)
attach(wss: WebSocket.Server) - Attach to WebSocket servergenerateSchema(resourceId, estimatedDuration, pricePerSecond?) - Generate WS402 schemagetSessionByUserId(userId) - Get active session by user IDgetActiveSessions() - Get all active sessionssession_end - Emitted when session endsrefund - Emitted when refund is issuedrefund_error - Emitted when refund failserror - Emitted on errorsSee detailed documentation:
# Base Blockchain
BASE_RPC=https://mainnet.base.org
MERCHANT_WALLET=0xYourWalletAddress
MERCHANT_PRIVATE_KEY=your_private_key_here
# Solana Blockchain
SOLANA_RPC=https://api.mainnet-beta.solana.com
SOLANA_NETWORK=mainnet-beta
MERCHANT_WALLET=YourSolanaPublicKey
MERCHANT_PRIVATE_KEY='[1,2,3,4,...,64]'
# Optional: SPL Token
SPL_TOKEN_MINT=EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
# Proxy Gateway
GATEWAY_URL=https://payment-gateway.example.com
GATEWAY_API_KEY=your-secret-api-key
# Server
PORT=4028
See .env.example for full configuration.
⚠️ CRITICAL: Never expose private keys in code or repositories!
// ❌ NEVER DO THIS
const provider = new BasePaymentProvider({
merchantPrivateKey: '0x123abc...'
});
// ✅ ALWAYS USE ENVIRONMENT VARIABLES
const provider = new BasePaymentProvider({
merchantPrivateKey: process.env.MERCHANT_PRIVATE_KEY
});
Best Practices:
See SECURITY.md for complete security guide.
[WS402 Server] → [Blockchain]
[Load Balancer]
↓
[WS402 Servers] → [Payment Gateway] → [Blockchain]
(private keys here)
See PROXY_ARCHITECTURE.md for deployment guide.
Contributions welcome! This is an open-source project.
git checkout -b feature/amazing)git commit -m 'Add amazing feature')git push origin feature/amazing)MIT License - see LICENSE file
Inspired by the X402 protocol and the need for fair, pay-as-you-go pricing for WebSocket resources.
Built with ❤️ for the open web and blockchain ecosystem
Client SDK, Claude plugin and skill framework for the Pinion protocol. x402 micropayments on Base.
Turn any API or MCP server into a paid service with x402
A wallet for agents. Make payments via x402, use stablecoins, swap assets, earn yield with defi and buy tokenized stocks across the most popular chains.
🛠 Enable seamless on-chain skill calls with automated x402 micropayments using the Pinion OS SDK and skill server framework.
Discover and pay AI agents on nullpath's marketplace via MCP. x402 micropayments with USDC.
Daydreams is a set of tools for building agents for commerce
Building blocks for Agentic payments (x402, MPP, AP2) for TypeScript, Rust, Go, Python, Ruby, PHP, Lua, Kotlin and Swift.
Building blocks for Agentic payments (x402, MPP, AP2) for TypeScript, Rust, Go, Python, Ruby, PHP, Lua, Kotlin and Swift.
Public adapters and discovery catalog for Triptych OS (Agent OS): agent frameworks, MCP/A2A/x402 protocols, workflows, wallets, SDKs, and examples for execute-first routing, governed handoffs, and receipt-aware agent commerce.
Developer integration tools for adding TDM payments to AI agents, apps, and workflows. CLI, MCP server, and copy-paste examples
Open contract-facing SDK for payable routes and gateway-backed integrations. Thin public surface for authorize, checkout, and session flows.
Developer integration tools for adding TDM payments to AI agents, apps, and workflows. CLI, MCP server, and copy-paste examples