Node.js Examples
Ready-to-use Node.js code for common Pixlpay API operations.
Setup
Using fetch (Node 18+)
javascript
class PixlpayClient {
constructor(subdomain, apiToken) {
this.baseUrl = `https://${subdomain}.pixlpay.net/api/external/v1`;
this.apiToken = apiToken;
}
async request(method, endpoint, data = null) {
const options = {
method,
headers: {
'Authorization': `Bearer ${this.apiToken}`,
'Accept': 'application/json',
'Content-Type': 'application/json',
},
};
if (data) {
options.body = JSON.stringify(data);
}
const response = await fetch(`${this.baseUrl}${endpoint}`, options);
const json = await response.json();
return {
status: response.status,
data: json,
};
}
get(endpoint) {
return this.request('GET', endpoint);
}
post(endpoint, data) {
return this.request('POST', endpoint, data);
}
}
module.exports = PixlpayClient;Fetching Products
javascript
const PixlpayClient = require('./pixlpay-client');
const client = new PixlpayClient('yourstore', process.env.PIXLPAY_TOKEN);
async function listProducts() {
const response = await client.get('/products');
if (response.status === 200) {
response.data.data.forEach(product => {
console.log(`${product.name} - $${product.price}`);
});
}
}
// Get single product
async function getProduct(id) {
const response = await client.get(`/products/${id}`);
return response.data.data;
}Fetching Orders
javascript
const client = new PixlpayClient('yourstore', process.env.PIXLPAY_TOKEN);
// Get pending orders
async function getPendingOrders() {
const response = await client.get('/orders?status=pending');
return response.data.data;
}
// Get orders by date range
async function getOrdersByDate(from, to) {
const response = await client.get(`/orders?from=${from}&to=${to}`);
return response.data.data;
}Fulfilling Orders
javascript
const client = new PixlpayClient('yourstore', process.env.PIXLPAY_TOKEN);
async function fulfillOrder(orderId) {
const response = await client.post(`/orders/${orderId}/fulfill`, {
note: 'Delivered via API'
});
if (response.status === 200) {
console.log('Order fulfilled successfully!');
return true;
}
return false;
}Webhook Signature Verification
Pixlpay signs all webhooks with HMAC-SHA256. Always verify signatures before processing webhook payloads.
Signature Verification Function
javascript
const crypto = require('crypto');
/**
* Verify webhook signature using HMAC-SHA256.
*
* @param {Buffer|string} payload - Raw request body
* @param {string} signature - Value from X-Webhook-Signature header
* @param {string} secret - Your webhook secret
* @returns {boolean} True if signature is valid
*/
function verifyWebhookSignature(payload, signature, secret) {
// Compute HMAC-SHA256 signature
const computed = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// Use timing-safe comparison to prevent timing attacks
try {
return crypto.timingSafeEqual(
Buffer.from(computed, 'utf8'),
Buffer.from(signature, 'utf8')
);
} catch (e) {
// Lengths don't match - signatures are definitely different
return false;
}
}
module.exports = { verifyWebhookSignature };Express.js Webhook Handler
javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
/**
* Verify webhook signature using HMAC-SHA256.
*/
function verifyWebhookSignature(payload, signature, secret) {
const computed = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(computed, 'utf8'),
Buffer.from(signature, 'utf8')
);
} catch (e) {
return false;
}
}
// IMPORTANT: Use express.raw() to get the raw body before JSON parsing
// This must come BEFORE any express.json() middleware for this route
app.post('/webhooks/pixlpay',
express.raw({ type: 'application/json' }),
(req, res) => {
// Get headers
const signature = req.headers['x-webhook-signature'] || '';
const eventType = req.headers['x-webhook-event'] || '';
const deliveryId = req.headers['x-webhook-id'] || '';
// Get secret from environment
const secret = process.env.PIXLPAY_WEBHOOK_SECRET;
// Verify signature FIRST
if (!verifyWebhookSignature(req.body, signature, secret)) {
console.error(`Invalid webhook signature for delivery: ${deliveryId}`);
return res.status(401).send('Invalid signature');
}
// Now safe to parse the payload
const event = JSON.parse(req.body.toString());
const data = event.data;
// Handle different events
switch (eventType) {
case 'order.received':
handleOrderReceived(data);
break;
case 'order.refunded':
handleOrderRefunded(data);
break;
case 'subscription.created':
handleSubscriptionCreated(data);
break;
case 'subscription.cancelled':
handleSubscriptionCancelled(data);
break;
case 'delivery.completed':
handleDeliveryCompleted(data);
break;
case 'delivery.failed':
handleDeliveryFailed(data);
break;
default:
console.log(`Unhandled event type: ${eventType}`);
}
// Respond quickly with 200
res.sendStatus(200);
}
);
// Event handlers
function handleOrderReceived(order) {
console.log(`Order received: ${order.order_number}`);
// Your delivery logic here
}
function handleOrderRefunded(order) {
console.log(`Order refunded: ${order.order_number}`);
// Revoke access, remove roles, etc.
}
function handleSubscriptionCreated(subscription) {
console.log(`Subscription created: ${subscription.id}`);
// Grant recurring access
}
function handleSubscriptionCancelled(subscription) {
console.log(`Subscription cancelled: ${subscription.id}`);
// Revoke recurring access
}
function handleDeliveryCompleted(delivery) {
console.log(`Delivery completed: ${delivery.id}`);
}
function handleDeliveryFailed(delivery) {
console.log(`Delivery failed: ${delivery.id}`);
// Alert admin, retry logic, etc.
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});Webhook Verification with Idempotency
Prevent replay attacks by tracking processed webhooks:
javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
// Simple in-memory store for demo - use Redis in production
const processedWebhooks = new Map();
// Clean up old entries every hour
setInterval(() => {
const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;
for (const [key, timestamp] of processedWebhooks) {
if (timestamp < oneDayAgo) {
processedWebhooks.delete(key);
}
}
}, 60 * 60 * 1000);
function verifyWebhookSignature(payload, signature, secret) {
const computed = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(computed, 'utf8'),
Buffer.from(signature, 'utf8')
);
} catch (e) {
return false;
}
}
app.post('/webhooks/pixlpay',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.headers['x-webhook-signature'] || '';
const deliveryId = req.headers['x-webhook-id'] || '';
const secret = process.env.PIXLPAY_WEBHOOK_SECRET;
// Verify signature
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
// Check for replay attack (idempotency)
if (processedWebhooks.has(deliveryId)) {
// Already processed - return success but don't reprocess
return res.status(200).send('Already processed');
}
// Process the webhook...
const event = JSON.parse(req.body.toString());
processEvent(event);
// Mark as processed
processedWebhooks.set(deliveryId, Date.now());
res.sendStatus(200);
}
);
function processEvent(event) {
console.log(`Processing event: ${event.event_type}`);
// Your processing logic here
}
app.listen(3000);Using with Redis for Production Idempotency
javascript
const express = require('express');
const crypto = require('crypto');
const Redis = require('ioredis');
const app = express();
const redis = new Redis(process.env.REDIS_URL);
function verifyWebhookSignature(payload, signature, secret) {
const computed = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
try {
return crypto.timingSafeEqual(
Buffer.from(computed, 'utf8'),
Buffer.from(signature, 'utf8')
);
} catch (e) {
return false;
}
}
app.post('/webhooks/pixlpay',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['x-webhook-signature'] || '';
const deliveryId = req.headers['x-webhook-id'] || '';
const secret = process.env.PIXLPAY_WEBHOOK_SECRET;
// Verify signature
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).send('Invalid signature');
}
// Check for replay attack using Redis
const cacheKey = `pixlpay_webhook:${deliveryId}`;
const exists = await redis.exists(cacheKey);
if (exists) {
return res.status(200).send('Already processed');
}
// Process the webhook
const event = JSON.parse(req.body.toString());
await processEvent(event);
// Mark as processed (expire after 24 hours)
await redis.setex(cacheKey, 86400, '1');
res.sendStatus(200);
}
);
async function processEvent(event) {
console.log(`Processing: ${event.event_type}`);
// Your async processing logic here
}
app.listen(3000);Creating Webhooks
javascript
const client = new PixlpayClient('yourstore', process.env.PIXLPAY_TOKEN);
async function createWebhook() {
const response = await client.post('/webhooks', {
url: 'https://yoursite.com/webhooks/pixlpay',
events: ['order.received', 'order.refunded', 'subscription.cancelled']
});
if (response.status === 201) {
const webhook = response.data.data;
console.log(`Webhook created! Secret: ${webhook.secret}`);
// IMPORTANT: Store this secret securely - you'll need it for verification
// Save to .env or secure config
return webhook;
}
}Analytics
javascript
const client = new PixlpayClient('yourstore', process.env.PIXLPAY_TOKEN);
async function getRevenueStats() {
const response = await client.get('/analytics/revenue?from=2025-01-01&to=2025-01-31');
if (response.status === 200) {
const { total_revenue, total_orders, average_order_value } = response.data.data;
console.log(`Revenue: $${total_revenue}`);
console.log(`Orders: ${total_orders}`);
console.log(`AOV: $${average_order_value}`);
}
}Error Handling
javascript
const client = new PixlpayClient('yourstore', process.env.PIXLPAY_TOKEN);
async function safeRequest() {
try {
const response = await client.get('/products/99999');
if (response.status !== 200) {
console.error(`Error: ${response.data.message}`);
switch (response.status) {
case 401:
console.error('Check your API token');
break;
case 404:
console.error('Resource not found');
break;
case 429:
console.error('Rate limited - slow down!');
break;
}
}
} catch (error) {
console.error('Network error:', error.message);
}
}