Webhooks provide an alternative to WebSocket connections for receiving trade alerts. Instead of you connecting to us, we send HTTP POST requests to your server whenever trades occur on wallets you're watching. This guide covers everything you need to set up, secure, and use webhooks effectively.
WebSocket vs Webhook: When to Use Each
Before diving into webhooks, understand when to use each approach:
WebSocket Alerts
- Lowest latency (milliseconds)
- Persistent connection required
- Best for real-time trading bots
- Requires connection management
- Single connection for all alerts
Webhook Notifications
- Low latency (seconds)
- Stateless HTTP requests
- Best for notifications & logging
- No connection management
- Multiple endpoints supported
Recommendation:
Use WebSockets for copy trading bots and latency-sensitive applications. Use Webhooks for Telegram/Discord notifications, logging systems, analytics pipelines, and as a backup notification method.
Prerequisites
What You'll Need:
Setting Up Webhooks
Step 1: Create a Webhook via API
Create a webhook by sending a POST request to the webhooks endpoint:
curl -X POST https://perpstracker.com:5001/api/webhooks \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-server.com/webhook",
"events": ["trade.new", "trade.close", "trade.liquidation"]
}'
Response:
{
"success": true,
"webhook": {
"id": "wh_abc123",
"url": "https://your-server.com/webhook",
"events": ["trade.new", "trade.close", "trade.liquidation"],
"secret": "whsec_xxxxxxxxxxxxxxxxxxx"
},
"message": "Webhook created. Save your secret - it will only be shown once!"
}
Save Your Secret!
The webhook secret is only shown once when creating the webhook. Store it securely—you'll need it to verify webhook signatures. If you lose it, you can regenerate it, but you'll need to update your verification code.
Step 2: Available Event Types
| Event Type | Description |
|---|---|
trade.new |
Triggered when a watched trader opens a new position |
trade.close |
Triggered when a watched trader closes a position |
trade.liquidation |
Triggered when a watched trader is liquidated |
Webhook Payload Format
When a trade event occurs, we send a POST request to your webhook URL with the following format:
{
"id": "evt_abc123xyz",
"type": "trade.new",
"created": 1705312800,
"data": {
"owner": "DRVRvzf8pSiMJi5BY1NmZu5STahBYKMVcfkPp3vxPump",
"marketSymbol": "SOL-PERP",
"side": "long",
"sizeUsd": 15000.50,
"leverage": 5.2,
"entryPrice": 185.42,
"collateral": 2884.71,
"timestamp": "2025-01-15T10:30:00Z"
}
}
Request Headers
| Header | Description |
|---|---|
Content-Type |
application/json |
X-Webhook-Signature |
HMAC-SHA256 signature for payload verification |
X-Webhook-ID |
Your webhook ID |
X-Event-Type |
The event type (e.g., trade.new) |
X-Delivery-ID |
Unique ID for this delivery attempt |
Verifying Webhook Signatures
Always verify webhook signatures to ensure requests are genuinely from PerpsTracker. We sign each payload using HMAC-SHA256 with your webhook secret.
Node.js Verification
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// Express middleware example
app.post('/webhook', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const payload = JSON.stringify(req.body);
if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process the verified webhook
console.log('Verified webhook:', req.body);
res.status(200).send('OK');
});
Python Verification
import hmac
import hashlib
def verify_webhook_signature(payload: str, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload.encode(),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
# Flask example
@app.route('/webhook', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Webhook-Signature')
payload = request.get_data(as_text=True)
if not verify_webhook_signature(payload, signature, WEBHOOK_SECRET):
return 'Invalid signature', 401
data = request.get_json()
print(f"Verified webhook: {data}")
return 'OK', 200
Security Note:
Always use timing-safe comparison functions (timingSafeEqual in Node.js, hmac.compare_digest in Python) to prevent timing attacks.
Building a Webhook Server
Here's a complete Express.js server that receives webhooks and sends Discord notifications:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
const DISCORD_WEBHOOK = process.env.DISCORD_WEBHOOK_URL;
function verifySignature(payload, signature) {
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(JSON.stringify(payload))
.digest('hex');
return signature === expected;
}
async function sendDiscordNotification(trade) {
const emoji = trade.side === 'long' ? '🟢' : '🔴';
const message = {
embeds: [{
title: `${emoji} ${trade.side.toUpperCase()} ${trade.marketSymbol}`,
color: trade.side === 'long' ? 0x10b981 : 0xef4444,
fields: [
{ name: 'Size', value: `$${trade.sizeUsd.toLocaleString()}`, inline: true },
{ name: 'Leverage', value: `${trade.leverage}x`, inline: true },
{ name: 'Entry', value: `$${trade.entryPrice}`, inline: true },
{ name: 'Trader', value: `\`${trade.owner.slice(0, 8)}...\`` }
],
timestamp: trade.timestamp
}]
};
await fetch(DISCORD_WEBHOOK, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message)
});
}
app.post('/webhook', async (req, res) => {
const signature = req.headers['x-webhook-signature'];
if (!verifySignature(req.body, signature)) {
console.log('Invalid webhook signature');
return res.status(401).send('Unauthorized');
}
const { type, data } = req.body;
console.log(`Received ${type} event`);
try {
if (type === 'trade.new') {
await sendDiscordNotification(data);
}
res.status(200).send('OK');
} catch (error) {
console.error('Error processing webhook:', error);
res.status(500).send('Error');
}
});
app.listen(3000, () => {
console.log('Webhook server running on port 3000');
});
Retry Policy
We automatically retry failed webhook deliveries with exponential backoff:
| Attempt | Delay | Total Time Since First Attempt |
|---|---|---|
| 1st (initial) | Immediate | 0 seconds |
| 2nd retry | 30 seconds | 30 seconds |
| 3rd retry | 60 seconds | ~1.5 minutes |
What Counts as Success?
A delivery is successful when your endpoint returns a 2xx status code (200, 201, 202, etc.). Any other status code (or timeout after 10 seconds) triggers a retry.
Managing Webhooks
List Your Webhooks
curl -X GET https://perpstracker.com:5001/api/webhooks \
-H "X-API-Key: YOUR_API_KEY"
Update a Webhook
curl -X PUT https://perpstracker.com:5001/api/webhooks/wh_abc123 \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://new-server.com/webhook",
"events": ["trade.new"],
"is_active": true
}'
Delete a Webhook
curl -X DELETE https://perpstracker.com:5001/api/webhooks/wh_abc123 \
-H "X-API-Key: YOUR_API_KEY"
Test a Webhook
Send a test event to verify your endpoint is working:
curl -X POST https://perpstracker.com:5001/api/webhooks/wh_abc123/test \
-H "X-API-Key: YOUR_API_KEY"
View Delivery History
Debug webhook issues by viewing recent deliveries:
curl -X GET "https://perpstracker.com:5001/api/webhooks/wh_abc123/deliveries?limit=20" \
-H "X-API-Key: YOUR_API_KEY"
Best Practices
Production Webhook Checklist:
Handling Duplicates (Idempotency)
Due to retries, you may receive the same event multiple times. Use the delivery ID to ensure idempotent processing:
const processedDeliveries = new Set();
app.post('/webhook', (req, res) => {
const deliveryId = req.headers['x-delivery-id'];
// Check if we already processed this delivery
if (processedDeliveries.has(deliveryId)) {
console.log(`Duplicate delivery: ${deliveryId}`);
return res.status(200).send('Already processed');
}
// Process the webhook...
processedDeliveries.add(deliveryId);
// Clean up old entries periodically
if (processedDeliveries.size > 10000) {
const oldest = processedDeliveries.values().next().value;
processedDeliveries.delete(oldest);
}
res.status(200).send('OK');
});
Common Issues
| Issue | Solution |
|---|---|
| Webhooks not arriving | Check your endpoint is publicly accessible; verify firewall rules |
| Signature verification fails | Ensure you're using the raw request body, not parsed JSON |
| Timeouts | Respond within 10 seconds; process heavy work asynchronously |
| SSL certificate errors | Use a valid SSL certificate from a trusted CA |
| Webhook disabled automatically | Too many failures; fix your endpoint and re-enable |
Need Help?
Check our API documentation for complete endpoint references. Join our Discord community for support.
Related Articles
- WebSocket Alerts: Getting Started Guide - For real-time streaming alerts
- Building a Copy Trading Bot - Advanced automation tutorial
- Copy Trading Guide - Strategies for following top traders