Webhooks
Webhooks allow your application to receive real-time notifications when specific events occur in the M.O.N.K.Y ecosystem. Instead of continuously polling our API, webhooks push data to your endpoints as events happen.
Overview
M.O.N.K.Y 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/monky' , ( req , res ) => {
const { event , data , signature , timestamp } = req . body ;
// Verify webhook signature
if ( ! verifySignature ( req . body , req . headers [ 'x-monky-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 M.O.N.K.Y API:
POST /v1/webhooks
Content-Type : application/json
Authorization : Bearer YOUR_API_TOKEN
{
"url" : "https://your-domain.com/webhook/monky" ,
"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/monky" ,
"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
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"
}
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"
}
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"
}
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
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"
}
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"
}
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
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"
}
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
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"
}
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"
}
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_TOKEN
Response:
{
"success" : true ,
"data" : {
"webhooks" : [
{
"webhook_id" : "wh_123456789" ,
"url" : "https://your-domain.com/webhook/monky" ,
"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/monky" ,
"events" : [ "transaction.confirmed" , "price.alert" ],
"active" : true
}
Delete Webhook
DELETE /v1/webhooks/{webhook_id}
Authorization : Bearer YOUR_API_TOKEN
Test Webhook
Send a test payload to verify your endpoint:
POST /v1/webhooks/{webhook_id}/test
Authorization : Bearer YOUR_API_TOKEN
Best 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/monky' , ( 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/monky' , ( req , res ) => {
const signature = req . headers [ 'x-monky-signature' ];
const timestamp = req . headers [ 'x-monky-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
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/monky' , ( 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.