Webhooks
Webhooks allow your application to receive real-time notifications when specific events occur in the N.0.M.A.D ecosystem. Instead of continuously polling our API, webhooks push data to your endpoints as events happen.
Overview
N.0.M.A.D webhooks are HTTP callbacks that we send to your specified endpoints when triggered events occur. This enables you to:
- Get instant notifications about transaction confirmations
- Receive real-time balance updates
- Monitor staking reward distributions
- Track price movements and alerts
- Handle user authentication events
Getting Started
1. Set Up Your Endpoint
Create an HTTPS endpoint on your server to receive webhook payloads:
// Express.js example
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook/n0mad', (req, res) => {
const { event, data, signature, timestamp } = req.body;
// Verify webhook signature
if (!verifySignature(req.body, req.headers['x-n0mad-signature'])) {
return res.status(401).send('Unauthorized');
}
// Process the webhook event
handleWebhookEvent(event, data);
res.status(200).send('OK');
});
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});2. Register Your Webhook
Register your endpoint with the N.0.M.A.D API:
POST /v1/webhooks
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
{
"url": "https://your-domain.com/webhook/n0mad",
"events": [
"transaction.confirmed",
"balance.updated",
"price.alert",
"stake.reward"
],
"secret": "your-webhook-secret",
"active": true
}Response:
{
"success": true,
"data": {
"webhook_id": "wh_123456789",
"url": "https://your-domain.com/webhook/n0mad",
"events": ["transaction.confirmed", "balance.updated"],
"created_at": "2024-10-24T07:37:42Z",
"active": true
}
}3. Verify Webhook Signatures
Always verify webhook signatures to ensure authenticity:
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(JSON.stringify(payload))
.digest('hex');
return `sha256=${expectedSignature}` === signature;
}Event Types
Transaction Events
transaction.pending
Triggered when a transaction is submitted to the network
{
"event": "transaction.pending",
"data": {
"transaction_id": "tx_abc123",
"wallet_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"amount": "1.5",
"token": "SOL",
"type": "send",
"recipient": "7xKxVqNqTgK9QhQVsQRAyrZzDsGYdLVL9zYtAWWB",
"submitted_at": "2024-10-24T07:37:42Z"
},
"timestamp": "2024-10-24T07:37:42Z"
}transaction.confirmed
Triggered when a transaction receives network confirmation
{
"event": "transaction.confirmed",
"data": {
"transaction_id": "tx_abc123",
"signature": "5J7...9xY",
"wallet_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"amount": "1.5",
"token": "SOL",
"fee": "0.000005",
"type": "send",
"recipient": "7xKxVqNqTgK9QhQVsQRAyrZzDsGYdLVL9zYtAWWB",
"block_height": 234567890,
"confirmations": 32,
"confirmed_at": "2024-10-24T07:38:15Z"
},
"timestamp": "2024-10-24T07:38:15Z"
}transaction.failed
Triggered when a transaction fails or is rejected
{
"event": "transaction.failed",
"data": {
"transaction_id": "tx_def456",
"wallet_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"amount": "1.5",
"token": "SOL",
"type": "send",
"error_code": "INSUFFICIENT_BALANCE",
"error_message": "Insufficient balance for transaction",
"failed_at": "2024-10-24T07:37:45Z"
},
"timestamp": "2024-10-24T07:37:45Z"
}transaction.received
Triggered when the wallet receives an incoming transaction
{
"event": "transaction.received",
"data": {
"transaction_id": "tx_ghi789",
"signature": "3K8...7zA",
"wallet_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"amount": "2.0",
"token": "SOL",
"sender": "4pQrNmKxVqNqTgK9QhQVsQRAyrZzDsGYdLVL9zYt",
"received_at": "2024-10-24T07:39:00Z"
},
"timestamp": "2024-10-24T07:39:00Z"
}Wallet Events
balance.updated
Triggered when wallet balance changes
{
"event": "balance.updated",
"data": {
"wallet_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"balances": {
"SOL": {
"amount": "4.25",
"usd_value": "850.00",
"change": "+0.5"
},
"USDC": {
"amount": "1250.00",
"usd_value": "1250.00",
"change": "0"
}
},
"total_usd_value": "2100.00",
"updated_at": "2024-10-24T07:39:00Z"
},
"timestamp": "2024-10-24T07:39:00Z"
}wallet.connected
Triggered when a new wallet is connected to the platform
{
"event": "wallet.connected",
"data": {
"user_id": "usr_12345",
"wallet_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"wallet_type": "phantom",
"connected_at": "2024-10-24T07:30:00Z"
},
"timestamp": "2024-10-24T07:30:00Z"
}wallet.disconnected
Triggered when a wallet is disconnected from the platform
{
"event": "wallet.disconnected",
"data": {
"user_id": "usr_12345",
"wallet_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"disconnected_at": "2024-10-24T08:30:00Z",
"reason": "user_initiated"
},
"timestamp": "2024-10-24T08:30:00Z"
}Price Alert Events
price.alert
Triggered when a price alert condition is met
{
"event": "price.alert",
"data": {
"alert_id": "alert_789",
"user_id": "usr_12345",
"token": "SOL",
"condition": "above",
"target_price": "200.00",
"current_price": "201.50",
"triggered_at": "2024-10-24T07:45:00Z"
},
"timestamp": "2024-10-24T07:45:00Z"
}price.significant_change
Triggered when token prices have significant movements
{
"event": "price.significant_change",
"data": {
"token": "SOL",
"previous_price": "200.00",
"current_price": "190.00",
"change_percent": -5.0,
"change_amount": "-10.00",
"timeframe": "1h",
"triggered_at": "2024-10-24T08:00:00Z"
},
"timestamp": "2024-10-24T08:00:00Z"
}Staking Events
stake.delegated
Triggered when SOL is delegated to a validator
{
"event": "stake.delegated",
"data": {
"user_id": "usr_12345",
"wallet_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"validator_address": "7K2nUkdq3BkVGhQRaqBiLxfpGpPy7YF8...",
"amount": "10.0",
"transaction_signature": "4kK7...8nR",
"delegated_at": "2024-10-24T07:50:00Z"
},
"timestamp": "2024-10-24T07:50:00Z"
}stake.reward
Triggered when staking rewards are distributed
{
"event": "stake.reward",
"data": {
"user_id": "usr_12345",
"wallet_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"reward_amount": "0.1234",
"epoch": 487,
"validator_address": "7K2nUkdq3BkVGhQRaqBiLxfpGpPy7YF8...",
"distributed_at": "2024-10-24T08:15:00Z"
},
"timestamp": "2024-10-24T08:15:00Z"
}stake.undelegated
Triggered when stake is undelegated from a validator
{
"event": "stake.undelegated",
"data": {
"user_id": "usr_12345",
"wallet_address": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"validator_address": "7K2nUkdq3BkVGhQRaqBiLxfpGpPy7YF8...",
"amount": "5.0",
"transaction_signature": "9mP3...2xT",
"cooldown_end": "2024-10-27T08:15:00Z",
"undelegated_at": "2024-10-24T08:15:00Z"
},
"timestamp": "2024-10-24T08:15:00Z"
}Webhook Management
List All Webhooks
GET /v1/webhooks
Authorization: Bearer YOUR_API_TOKENResponse:
{
"success": true,
"data": {
"webhooks": [
{
"webhook_id": "wh_123456789",
"url": "https://your-domain.com/webhook/n0mad",
"events": ["transaction.confirmed", "balance.updated"],
"active": true,
"created_at": "2024-10-24T07:37:42Z",
"last_triggered": "2024-10-24T09:15:00Z"
}
],
"total_count": 1
}
}Update Webhook
PUT /v1/webhooks/{webhook_id}
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
{
"url": "https://new-domain.com/webhook/n0mad",
"events": ["transaction.confirmed", "price.alert"],
"active": true
}Delete Webhook
DELETE /v1/webhooks/{webhook_id}
Authorization: Bearer YOUR_API_TOKENTest Webhook
Send a test payload to verify your endpoint:
POST /v1/webhooks/{webhook_id}/test
Authorization: Bearer YOUR_API_TOKENBest Practices
1. Handle Idempotency
Webhooks may be delivered multiple times. Use the transaction_id or event ID to implement idempotency:
const processedEvents = new Set();
function handleWebhookEvent(event, data) {
const eventId = data.transaction_id || data.alert_id || data.user_id;
if (processedEvents.has(eventId)) {
console.log('Event already processed:', eventId);
return;
}
// Process the event
processEvent(event, data);
// Mark as processed
processedEvents.add(eventId);
}2. Implement Retry Logic
Handle failed webhook deliveries gracefully:
app.post('/webhook/n0mad', (req, res) => {
try {
// Process webhook
handleWebhookEvent(req.body.event, req.body.data);
res.status(200).send('OK');
} catch (error) {
console.error('Webhook processing failed:', error);
// Return 5xx status to trigger retry
res.status(500).send('Internal Server Error');
}
});3. Secure Your Endpoint
Always verify webhook signatures and use HTTPS:
app.post('/webhook/n0mad', (req, res) => {
const signature = req.headers['x-n0mad-signature'];
const timestamp = req.headers['x-n0mad-timestamp'];
// Verify timestamp (prevent replay attacks)
if (Date.now() - parseInt(timestamp) > 300000) { // 5 minutes
return res.status(401).send('Request too old');
}
// Verify signature
if (!verifySignature(req.body, signature, process.env.WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process webhook...
});4. Log Webhook Events
Maintain detailed logs for debugging and monitoring:
function handleWebhookEvent(event, data) {
console.log('Webhook received:', {
event,
timestamp: new Date().toISOString(),
data: JSON.stringify(data)
});
try {
processEvent(event, data);
console.log('Webhook processed successfully:', event);
} catch (error) {
console.error('Webhook processing error:', error);
throw error;
}
}Rate Limits
Webhook deliveries are subject to rate limits:
- Maximum Attempts: 5 retry attempts per webhook
- Retry Delays: Exponential backoff (1s, 2s, 4s, 8s, 16s)
- Timeout: 30 seconds per delivery attempt
- Maximum Events: 10,000 webhook events per hour per endpoint
Troubleshooting
Common Issues
Webhook Not Receiving Events
Possible Causes:
- Endpoint URL not accessible from the internet
- Firewall blocking incoming requests
- SSL certificate issues with HTTPS endpoint
- Webhook is inactive or misconfigured
Solutions:
- Test endpoint accessibility with tools like ngrok for development
- Check webhook status via API
- Verify SSL certificate is valid
- Use webhook test endpoint to verify configuration
Signature Verification Failing
Possible Causes:
- Incorrect webhook secret
- Body parsing changing the payload
- Clock synchronization issues
Solutions:
- Verify webhook secret matches registered value
- Use raw body for signature verification
- Check system time synchronization
Duplicate Events
Possible Causes:
- Network issues causing retries
- Multiple webhook endpoints registered
- Processing errors causing failed deliveries
Solutions:
- Implement idempotency using event IDs
- Check for duplicate webhook registrations
- Ensure proper HTTP status codes in responses
Webhook Monitoring
Monitor webhook health and performance:
// Webhook metrics tracking
const webhookMetrics = {
totalReceived: 0,
totalProcessed: 0,
totalErrors: 0,
lastReceived: null,
processingTimes: []
};
app.post('/webhook/n0mad', (req, res) => {
const startTime = Date.now();
webhookMetrics.totalReceived++;
webhookMetrics.lastReceived = new Date().toISOString();
try {
handleWebhookEvent(req.body.event, req.body.data);
webhookMetrics.totalProcessed++;
res.status(200).send('OK');
} catch (error) {
webhookMetrics.totalErrors++;
res.status(500).send('Error');
} finally {
const processingTime = Date.now() - startTime;
webhookMetrics.processingTimes.push(processingTime);
// Keep only last 100 processing times
if (webhookMetrics.processingTimes.length > 100) {
webhookMetrics.processingTimes.shift();
}
}
});
// Health check endpoint
app.get('/webhook/health', (req, res) => {
res.json(webhookMetrics);
});Need help with webhooks? Check our FAQ or contact our technical support team.
Development Tip: Use tools like ngrok or webhook.site for testing webhooks during development.