Webhooks Guide
This guide covers setting up and handling webhooks in your TraderPal Connect integration.
Overview
Webhooks allow your application to receive real-time updates about various events:
- Order status changes
- Trade executions
- Account updates
- Payment processing
- KYC status changes
Webhook Setup
Register Webhook Endpoint
const webhook = await client.webhooks.create({
url: 'https://your-app.com/webhooks',
events: ['order.executed', 'account.updated'],
description: 'Production webhook endpoint'
});
console.log(`Webhook ID: ${webhook.id}`);
Webhook Authentication
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
Event Types
Order Events
{
"type": "order.executed",
"id": "evt_123456789",
"created": "2024-01-01T00:00:00Z",
"data": {
"orderId": "ord_123456789",
"status": "filled",
"symbol": "AAPL",
"quantity": 10,
"price": 150.00,
"side": "buy",
"executedAt": "2024-01-01T00:00:00Z"
}
}
Account Events
{
"type": "account.updated",
"id": "evt_123456789",
"created": "2024-01-01T00:00:00Z",
"data": {
"accountId": "acc_123456789",
"status": "active",
"balance": 10000.00,
"currency": "USD",
"updatedAt": "2024-01-01T00:00:00Z"
}
}
Payment Events
{
"type": "payment.processed",
"id": "evt_123456789",
"created": "2024-01-01T00:00:00Z",
"data": {
"paymentId": "pmt_123456789",
"status": "completed",
"amount": 1000.00,
"currency": "USD",
"type": "deposit",
"processedAt": "2024-01-01T00:00:00Z"
}
}
Webhook Handler
Express Implementation
const express = require('express');
const app = express();
app.post('/webhooks', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-tp-signature'];
if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(req.body);
// Process event based on type
switch (event.type) {
case 'order.executed':
handleOrderExecution(event.data);
break;
case 'account.updated':
handleAccountUpdate(event.data);
break;
case 'payment.processed':
handlePaymentProcessed(event.data);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.status(200).send('Webhook processed');
});
Event Handlers
async function handleOrderExecution(data) {
try {
// Update local order status
await db.orders.update({
orderId: data.orderId,
status: data.status,
executedQuantity: data.quantity,
executedPrice: data.price
});
// Notify user
await notifyUser({
userId: data.userId,
type: 'order_executed',
message: `Order ${data.orderId} has been executed`
});
// Update positions
await updatePositions(data);
} catch (error) {
console.error('Error handling order execution:', error);
// Implement retry logic or alert system
}
}
async function handleAccountUpdate(data) {
try {
// Update account information
await db.accounts.update({
accountId: data.accountId,
status: data.status,
balance: data.balance
});
// Check for important changes
if (data.status === 'margin_call') {
await handleMarginCall(data);
}
} catch (error) {
console.error('Error handling account update:', error);
}
}
async function handlePaymentProcessed(data) {
try {
// Update payment status
await db.payments.update({
paymentId: data.paymentId,
status: data.status
});
// Handle successful payment
if (data.status === 'completed') {
await updateAccountBalance(data);
await notifyUser({
userId: data.userId,
type: 'payment_success',
message: `Payment of ${data.amount} ${data.currency} has been processed`
});
}
} catch (error) {
console.error('Error handling payment:', error);
}
}
Retry Logic
Webhook Retry Handler
class WebhookRetryHandler {
constructor(maxRetries = 3, initialDelay = 1000) {
this.maxRetries = maxRetries;
this.initialDelay = initialDelay;
}
async processWithRetry(eventId, handler) {
let attempts = 0;
let lastError;
while (attempts < this.maxRetries) {
try {
await handler();
return;
} catch (error) {
lastError = error;
attempts++;
if (attempts < this.maxRetries) {
await this.wait(this.getDelay(attempts));
}
}
}
// Log failed event for manual review
await this.logFailedEvent(eventId, lastError);
}
getDelay(attempt) {
// Exponential backoff
return this.initialDelay * Math.pow(2, attempt - 1);
}
wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async logFailedEvent(eventId, error) {
await db.failedWebhooks.create({
eventId,
error: error.message,
timestamp: new Date()
});
}
}
Event Queue
Queue Implementation
const Queue = require('bull');
const webhookQueue = new Queue('webhooks', {
redis: {
port: 6379,
host: 'localhost'
}
});
// Add event to queue
app.post('/webhooks', async (req, res) => {
const event = req.body;
await webhookQueue.add(event, {
attempts: 3,
backoff: {
type: 'exponential',
delay: 1000
}
});
res.status(200).send('Event queued');
});
// Process events
webhookQueue.process(async (job) => {
const event = job.data;
switch (event.type) {
case 'order.executed':
await handleOrderExecution(event.data);
break;
// Handle other events
}
});
// Handle failed jobs
webhookQueue.on('failed', async (job, error) => {
console.error(`Job ${job.id} failed:`, error);
await logFailedJob(job, error);
});
Monitoring
Health Checks
function checkWebhookHealth() {
return {
queueSize: webhookQueue.count(),
failedJobs: webhookQueue.getFailedCount(),
processingTime: getAverageProcessingTime(),
lastProcessedEvent: getLastProcessedEvent()
};
}
// Expose health endpoint
app.get('/webhooks/health', async (req, res) => {
const health = await checkWebhookHealth();
res.json(health);
});
Best Practices
-
Reliability
- Implement retry logic
- Use event queues
- Handle failures gracefully
- Monitor processing
-
Security
- Verify signatures
- Use HTTPS
- Validate payload
- Secure secrets
-
Performance
- Process async
- Use queues
- Handle rate limits
- Monitor latency
-
Maintenance
- Log events
- Monitor health
- Clean old data
- Update endpoints
Webhook Testing
Test Event
const testEvent = {
type: 'order.executed',
id: 'evt_test_123',
created: new Date().toISOString(),
data: {
orderId: 'ord_test_123',
status: 'filled',
symbol: 'AAPL',
quantity: 10,
price: 150.00
}
};
// Sign test event
const signature = signPayload(testEvent, process.env.WEBHOOK_SECRET);
// Send test webhook
await axios.post('https://your-app.com/webhooks', testEvent, {
headers: {
'Content-Type': 'application/json',
'X-TP-Signature': signature
}
});