JavaScript/TypeScript
Complete examples for accepting x402 payments with Interface402 using JavaScript and TypeScript.
Installation
# No special installation needed! Just use fetch or your favorite HTTP library
npm install node-fetch # If using Node.js < 18
npm install ws # For WebSocket supportBasic Payment Verification
Verify a Payment
async function verifyPayment(
paymentProof: string,
expectedAmount: number,
recipientAddress: string
) {
const response = await fetch('https://api.interface402.dev/v1/payments/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
body: JSON.stringify({
payment_proof: paymentProof,
amount: expectedAmount,
recipient: recipientAddress
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(`Verification failed: ${error.message}`);
}
const data = await response.json();
return data;
}
// Usage
const result = await verifyPayment(
'BASE64_ENCODED_PAYMENT_PROOF',
1000000,
'YOUR_WALLET_ADDRESS'
);
if (result.verified) {
console.log('Payment verified!');
console.log('Transaction ID:', result.transaction_id);
console.log('Amount:', result.amount);
} else {
console.log('Payment invalid');
}Express.js Integration
Protect an API Endpoint
import express from 'express';
const app = express();
app.use(express.json());
// Helper function to verify payments
async function verifyPaymentWithInterface402(
paymentProof: string,
amount: number,
recipient: string
) {
const response = await fetch('https://api.interface402.dev/v1/payments/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
body: JSON.stringify({
payment_proof: paymentProof,
amount,
recipient
})
});
const data = await response.json();
return data.verified;
}
// Protected endpoint
app.get('/api/premium-content', async (req, res) => {
const paymentProof = req.headers['x-payment-proof'] as string;
const REQUIRED_AMOUNT = 1000000; // 0.001 SOL in lamports
const MY_WALLET = 'YOUR_WALLET_ADDRESS';
if (!paymentProof) {
return res.status(402).json({
error: 'Payment Required',
amount: REQUIRED_AMOUNT,
recipient: MY_WALLET,
message: 'Please provide x-payment-proof header'
});
}
try {
const verified = await verifyPaymentWithInterface402(
paymentProof,
REQUIRED_AMOUNT,
MY_WALLET
);
if (verified) {
// Payment verified, return the content
return res.json({
data: 'Your premium content here',
message: 'Access granted'
});
} else {
return res.status(402).json({
error: 'Invalid payment',
message: 'Payment verification failed'
});
}
} catch (error) {
console.error('Payment verification error:', error);
return res.status(500).json({
error: 'Internal server error',
message: 'Failed to verify payment'
});
}
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});Middleware for Payment Protection
import { Request, Response, NextFunction } from 'express';
interface PaymentRequirement {
amount: number;
recipient: string;
}
function requirePayment(config: PaymentRequirement) {
return async (req: Request, res: Response, next: NextFunction) => {
const paymentProof = req.headers['x-payment-proof'] as string;
if (!paymentProof) {
return res.status(402).json({
error: 'Payment Required',
amount: config.amount,
recipient: config.recipient
});
}
try {
const response = await fetch('https://api.interface402.dev/v1/payments/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
body: JSON.stringify({
payment_proof: paymentProof,
amount: config.amount,
recipient: config.recipient
})
});
const data = await response.json();
if (data.verified) {
// Attach payment info to request for later use
req.payment = data;
next();
} else {
return res.status(402).json({
error: 'Invalid payment'
});
}
} catch (error) {
return res.status(500).json({
error: 'Payment verification failed'
});
}
};
}
// Usage
app.get(
'/api/paid-content',
requirePayment({
amount: 1000000,
recipient: 'YOUR_WALLET_ADDRESS'
}),
(req, res) => {
res.json({ data: 'Your paid content' });
}
);WebSocket for Real-Time Notifications
Basic WebSocket Connection
import WebSocket from 'ws';
class PaymentStream {
private ws: WebSocket | null = null;
private apiKey: string;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
connect() {
this.ws = new WebSocket('wss://api.interface402.dev/v1/payments/stream');
this.ws.on('open', () => {
console.log('Connected to payment stream');
// Authenticate
this.send({
type: 'authenticate',
apiKey: this.apiKey
});
// Subscribe to your wallet's payments
this.send({
type: 'subscribe',
wallet: 'YOUR_WALLET_ADDRESS'
});
});
this.ws.on('message', (data: string) => {
const payment = JSON.parse(data);
this.handlePayment(payment);
});
this.ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
this.ws.on('close', () => {
console.log('Disconnected, reconnecting in 5s...');
setTimeout(() => this.connect(), 5000);
});
}
private send(data: any) {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(data));
}
}
private handlePayment(payment: any) {
console.log('Payment received:', {
amount: payment.amount,
from: payment.sender,
transactionId: payment.transaction_id
});
// Do something with the payment
// e.g., unlock content, send confirmation email, etc.
}
disconnect() {
this.ws?.close();
}
}
// Usage
const stream = new PaymentStream(process.env.API_KEY!);
stream.connect();React Hook for Payment Notifications
import { useEffect, useState } from 'react';
interface Payment {
transaction_id: string;
amount: number;
sender: string;
timestamp: number;
}
export function usePaymentStream(apiKey: string, walletAddress: string) {
const [payments, setPayments] = useState<Payment[]>([]);
const [connected, setConnected] = useState(false);
useEffect(() => {
const ws = new WebSocket('wss://api.interface402.dev/v1/payments/stream');
ws.onopen = () => {
setConnected(true);
ws.send(JSON.stringify({
type: 'authenticate',
apiKey
}));
ws.send(JSON.stringify({
type: 'subscribe',
wallet: walletAddress
}));
};
ws.onmessage = (event) => {
const payment = JSON.parse(event.data);
setPayments(prev => [payment, ...prev]);
};
ws.onclose = () => {
setConnected(false);
};
return () => ws.close();
}, [apiKey, walletAddress]);
return { payments, connected };
}
// Usage in component
function PaymentDashboard() {
const { payments, connected } = usePaymentStream(
process.env.NEXT_PUBLIC_API_KEY!,
'YOUR_WALLET_ADDRESS'
);
return (
<div>
<div>Status: {connected ? '🟢 Connected' : '🔴 Disconnected'}</div>
<h2>Recent Payments</h2>
<ul>
{payments.map(payment => (
<li key={payment.transaction_id}>
Received {payment.amount} from {payment.sender}
</li>
))}
</ul>
</div>
);
}Error Handling
Robust Error Handling
class PaymentVerificationError extends Error {
code: string;
details: any;
constructor(code: string, message: string, details: any = {}) {
super(message);
this.code = code;
this.details = details;
}
}
async function verifyPaymentWithRetry(
paymentProof: string,
amount: number,
recipient: string,
maxRetries: number = 3
) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch('https://api.interface402.dev/v1/payments/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
body: JSON.stringify({
payment_proof: paymentProof,
amount,
recipient
})
});
const data = await response.json();
if (!response.ok) {
throw new PaymentVerificationError(
data.error?.code || 'UNKNOWN_ERROR',
data.error?.message || 'Verification failed',
data.error?.details
);
}
return data;
} catch (error) {
if (error instanceof PaymentVerificationError) {
// Don't retry on certain errors
if (error.code === 'INVALID_PAYMENT_PROOF' || error.code === 'INVALID_AMOUNT') {
throw error;
}
// Retry on network errors or rate limits
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000;
console.log(`Retrying in ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
}
throw error;
}
}
throw new Error('Max retries exceeded');
}Complete Example: API with Payments
import express from 'express';
import { PaymentStream } from './payment-stream';
const app = express();
app.use(express.json());
// In-memory cache of verified payments (use Redis in production)
const verifiedPayments = new Set<string>();
// Start payment stream to track incoming payments
const paymentStream = new PaymentStream(process.env.API_KEY!);
paymentStream.on('payment', (payment) => {
verifiedPayments.add(payment.transaction_id);
console.log('Payment received:', payment.transaction_id);
});
paymentStream.connect();
// Verify payment endpoint
app.post('/api/verify-payment', async (req, res) => {
const { payment_proof, amount } = req.body;
try {
const response = await fetch('https://api.interface402.dev/v1/payments/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.API_KEY}`
},
body: JSON.stringify({
payment_proof,
amount,
recipient: 'YOUR_WALLET_ADDRESS'
})
});
const data = await response.json();
if (data.verified) {
// Store the payment ID
verifiedPayments.add(data.transaction_id);
return res.json({
success: true,
transaction_id: data.transaction_id
});
}
return res.status(402).json({
success: false,
error: 'Payment verification failed'
});
} catch (error) {
return res.status(500).json({
success: false,
error: 'Internal server error'
});
}
});
// Protected content endpoint
app.get('/api/content/:id', async (req, res) => {
const transactionId = req.headers['x-transaction-id'] as string;
if (!transactionId || !verifiedPayments.has(transactionId)) {
return res.status(402).json({
error: 'Payment Required',
message: 'Valid payment required to access this content'
});
}
// Return the content
res.json({
content: 'Your premium content here'
});
});
app.listen(3000);Best Practices
Environment Variables: Always store API keys in environment variables
Error Handling: Implement retry logic with exponential backoff
Logging: Log all payment verifications for debugging and reconciliation
Validation: Always validate payment amounts match what you expect
Security: Never expose API keys in client-side code
Testing: Test with small amounts first before going live
Next Steps
See Python examples for Python integration
Check out the API Reference for all endpoints
Read about error handling best practices
Last updated
