Documentation Index Fetch the complete documentation index at: https://docs.nudj.cx/llms.txt
Use this file to discover all available pages before exploring further.
Common Issues and Solutions
Webhook Not Being Received
Check Webhook Configuration
Verify your webhook is properly configured and enabled: curl -X GET "https://{subdomain}.nudj.cx/api/v2/admin/webhooks/configs" \
-H "x-api-token: YOUR_ADMIN_API_TOKEN"
Common issues:
isEnabled: false - Webhook is disabled
Empty events array - No events configured
Invalid URL format - Must be valid HTTPS URL
Missing or incorrect headers
Verify Endpoint Accessibility
Test if your webhook endpoint is reachable: # Test basic connectivity
curl -X POST "https://your-app.com/webhooks/nudj" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-webhook-secret" \
-d '{"test": "connectivity"}'
Requirements:
Must use HTTPS (not HTTP)
Must respond within 10 seconds
Must return HTTP 2xx for success
No redirects (301/302) allowed
Ensure the events you’re expecting are actually occurring: // Log all webhook configurations to verify event subscriptions
const configs = await fetch ( `https:// ${ subdomain } .nudj.cx/api/v2/admin/webhooks/configs` , {
headers: { 'x-api-token' : 'YOUR_TOKEN' }
}). then ( r => r . json ());
console . log ( 'Subscribed events:' , configs [ 0 ]?. events );
Verify:
Events are occurring in your community
Event names match exactly (case-sensitive)
User actions are completing successfully
Authentication Failures
Header Authentication Issues
Environment Variable Issues
Ensure your secrets are properly configured: // Debug environment variables
console . log ( 'Environment check:' , {
hasWebhookSecret: !! process . env . WEBHOOK_SECRET ,
secretLength: process . env . WEBHOOK_SECRET ?. length ,
hasApiToken: !! process . env . NUDJ_ADMIN_API_TOKEN
});
Best practices:
Use different secrets for development/production
Never commit secrets to version control
Use a secure secret generator
Rotate secrets regularly
Processing Errors
Webhooks must respond within 10 seconds: // Implement async processing for heavy workloads
app . post ( '/webhooks/nudj' , async ( req , res ) => {
try {
// Respond immediately
res . status ( 200 ). send ( 'OK' );
// Process asynchronously
setImmediate ( async () => {
try {
await processWebhookEvent ( req . body );
} catch ( error ) {
console . error ( 'Async webhook processing error:' , error );
}
});
} catch ( error ) {
console . error ( 'Webhook error:' , error );
res . status ( 500 ). send ( 'Error' );
}
});
Memory and Performance Issues
Handle malformed webhook payloads gracefully: app . use ( '/webhooks' , express . json ({
limit: '10mb' ,
verify : ( req , res , buf , encoding ) => {
try {
JSON . parse ( buf );
} catch ( error ) {
console . error ( 'Invalid JSON in webhook:' , error );
throw new Error ( 'Invalid JSON payload' );
}
}
}));
Debugging Techniques
Enable Detailed Logging
const winston = require ( 'winston' );
const logger = winston . createLogger ({
level: process . env . LOG_LEVEL || 'info' ,
format: winston . format . combine (
winston . format . timestamp (),
winston . format . errors ({ stack: true }),
winston . format . json ()
),
transports: [
new winston . transports . Console (),
new winston . transports . File ({ filename: 'webhook-debug.log' })
]
});
app . post ( '/webhooks/nudj' , async ( req , res ) => {
const webhookId = req . body ?. id || 'unknown' ;
logger . info ( 'Webhook received' , {
webhookId ,
headers: req . headers ,
body: req . body ,
timestamp: new Date (). toISOString ()
});
try {
// Verify signature
if ( ! verifyWebhookSignature ( req )) {
logger . warn ( 'Webhook signature verification failed' , {
webhookId ,
receivedAuth: req . get ( 'Authorization' ),
receivedSource: req . get ( 'X-Webhook-Source' )
});
return res . status ( 401 ). send ( 'Unauthorized' );
}
logger . info ( 'Webhook signature verified' , { webhookId });
// Process event
await processWebhookEvent ( req . body );
logger . info ( 'Webhook processed successfully' , { webhookId });
res . status ( 200 ). send ( 'OK' );
} catch ( error ) {
logger . error ( 'Webhook processing failed' , {
webhookId ,
error: error . message ,
stack: error . stack
});
res . status ( 500 ). send ( 'Internal Server Error' );
}
});
Request Inspector Middleware
function requestInspector ( req , res , next ) {
const originalSend = res . send ;
const startTime = Date . now ();
// Log incoming request
console . log ( '=== INCOMING WEBHOOK ===' );
console . log ( 'Method:' , req . method );
console . log ( 'URL:' , req . url );
console . log ( 'Headers:' , JSON . stringify ( req . headers , null , 2 ));
console . log ( 'Body:' , JSON . stringify ( req . body , null , 2 ));
console . log ( 'Timestamp:' , new Date (). toISOString ());
// Override res.send to log response
res . send = function ( data ) {
const duration = Date . now () - startTime ;
console . log ( '=== OUTGOING RESPONSE ===' );
console . log ( 'Status:' , res . statusCode );
console . log ( 'Duration:' , duration + 'ms' );
console . log ( 'Response:' , data );
console . log ( '========================' );
return originalSend . call ( this , data );
};
next ();
}
// Use only in development
if ( process . env . NODE_ENV === 'development' ) {
app . use ( '/webhooks' , requestInspector );
}
Local Testing Setup
Testing Script
// webhook-tester.js
const express = require ( 'express' );
const app = express ();
app . use ( express . json ());
app . post ( '/webhooks/nudj' , ( req , res ) => {
console . log ( '=== WEBHOOK TEST ===' );
console . log ( 'Headers:' , req . headers );
console . log ( 'Body:' , JSON . stringify ( req . body , null , 2 ));
console . log ( '==================' );
res . status ( 200 ). json ({
message: 'Webhook received successfully' ,
timestamp: new Date (). toISOString (),
webhookId: req . body ?. id
});
});
const port = process . env . PORT || 3000 ;
app . listen ( port , () => {
console . log ( `Webhook test server running on port ${ port } ` );
console . log ( `Use ngrok to expose: ngrok http ${ port } ` );
});
Error Monitoring and Alerting
Webhook Health Checks
class WebhookHealthMonitor {
constructor () {
this . stats = {
totalReceived: 0 ,
totalProcessed: 0 ,
totalFailed: 0 ,
lastProcessed: null ,
avgProcessingTime: 0
};
this . processingTimes = [];
this . maxSamples = 100 ;
}
recordWebhook ( processingTime , success ) {
this . stats . totalReceived ++ ;
if ( success ) {
this . stats . totalProcessed ++ ;
this . stats . lastProcessed = new Date ();
// Track processing time
this . processingTimes . push ( processingTime );
if ( this . processingTimes . length > this . maxSamples ) {
this . processingTimes . shift ();
}
this . stats . avgProcessingTime =
this . processingTimes . reduce (( a , b ) => a + b , 0 ) / this . processingTimes . length ;
} else {
this . stats . totalFailed ++ ;
}
}
getHealthStatus () {
const successRate = ( this . stats . totalProcessed / this . stats . totalReceived ) * 100 ;
const isHealthy = successRate > 95 && this . stats . avgProcessingTime < 1000 ;
return {
healthy: isHealthy ,
stats: this . stats ,
successRate: successRate . toFixed ( 2 ) + '%' ,
alerts: this . generateAlerts ()
};
}
generateAlerts () {
const alerts = [];
const successRate = ( this . stats . totalProcessed / this . stats . totalReceived ) * 100 ;
if ( successRate < 90 ) {
alerts . push ({
level: 'critical' ,
message: `Low success rate: ${ successRate . toFixed ( 2 ) } %`
});
}
if ( this . stats . avgProcessingTime > 5000 ) {
alerts . push ({
level: 'warning' ,
message: `High avg processing time: ${ this . stats . avgProcessingTime } ms`
});
}
if ( this . stats . lastProcessed && Date . now () - this . stats . lastProcessed > 3600000 ) {
alerts . push ({
level: 'warning' ,
message: 'No webhooks processed in last hour'
});
}
return alerts ;
}
}
const healthMonitor = new WebhookHealthMonitor ();
// Health check endpoint
app . get ( '/webhooks/health' , ( req , res ) => {
const health = healthMonitor . getHealthStatus ();
res . status ( health . healthy ? 200 : 503 ). json ( health );
});
Error Notifications
class WebhookAlertSystem {
constructor () {
this . errorThresholds = {
failureRate: 10 , // 10% failure rate triggers alert
consecutiveFailures: 5 ,
avgProcessingTime: 5000 // 5 seconds
};
this . consecutiveFailures = 0 ;
this . recentErrors = [];
}
async handleError ( error , webhookData ) {
this . consecutiveFailures ++ ;
this . recentErrors . push ({
timestamp: new Date (),
error: error . message ,
webhookId: webhookData . id ,
eventType: webhookData . eventSubCategory
});
// Keep only last 50 errors
if ( this . recentErrors . length > 50 ) {
this . recentErrors = this . recentErrors . slice ( - 50 );
}
// Check if we need to send alerts
await this . checkAlertThresholds ();
}
handleSuccess () {
this . consecutiveFailures = 0 ;
}
async checkAlertThresholds () {
if ( this . consecutiveFailures >= this . errorThresholds . consecutiveFailures ) {
await this . sendAlert ( 'critical' ,
` ${ this . consecutiveFailures } consecutive webhook failures` );
}
// Check failure rate in last 10 minutes
const tenMinutesAgo = new Date ( Date . now () - 10 * 60 * 1000 );
const recentErrors = this . recentErrors . filter ( e => e . timestamp > tenMinutesAgo );
if ( recentErrors . length >= this . errorThresholds . failureRate ) {
await this . sendAlert ( 'warning' ,
`High error rate: ${ recentErrors . length } errors in last 10 minutes` );
}
}
async sendAlert ( level , message ) {
const alertData = {
level ,
message ,
service: 'webhook-handler' ,
timestamp: new Date (). toISOString (),
recentErrors: this . recentErrors . slice ( - 5 ) // Last 5 errors
};
// Send to multiple channels
await Promise . all ([
this . sendSlackAlert ( alertData ),
this . sendEmailAlert ( alertData ),
this . logAlert ( alertData )
]);
}
async sendSlackAlert ( alertData ) {
// Implement Slack webhook notification
console . log ( 'Slack alert:' , alertData );
}
async sendEmailAlert ( alertData ) {
// Implement email notification
console . log ( 'Email alert:' , alertData );
}
async logAlert ( alertData ) {
console . error ( 'WEBHOOK ALERT:' , alertData );
}
}
const alertSystem = new WebhookAlertSystem ();
Async Processing
const Queue = require ( 'bull' );
const webhookQueue = new Queue ( 'webhook processing' );
// Add webhook to queue immediately
app . post ( '/webhooks/nudj' , async ( req , res ) => {
try {
// Verify signature quickly
if ( ! verifyWebhookSignature ( req )) {
return res . status ( 401 ). send ( 'Unauthorized' );
}
// Add to queue for async processing
await webhookQueue . add ( 'process' , req . body , {
attempts: 3 ,
backoff: 'exponential' ,
delay: 0
});
// Respond immediately
res . status ( 200 ). send ( 'OK' );
} catch ( error ) {
console . error ( 'Error queuing webhook:' , error );
res . status ( 500 ). send ( 'Error' );
}
});
// Process webhooks from queue
webhookQueue . process ( 'process' , async ( job ) => {
const webhookData = job . data ;
try {
await processWebhookEvent ( webhookData );
console . log ( `Processed webhook ${ webhookData . id } ` );
} catch ( error ) {
console . error ( `Failed to process webhook ${ webhookData . id } :` , error );
throw error ; // Will trigger retry
}
});
Caching Strategies
const NodeCache = require ( 'node-cache' );
const cache = new NodeCache ({ stdTTL: 300 }); // 5 minute TTL
async function processWebhookEvent ( event ) {
const cacheKey = `user_ ${ event . userId } ` ;
// Try to get user from cache
let user = cache . get ( cacheKey );
if ( ! user ) {
user = await getUserFromDatabase ( event . userId );
cache . set ( cacheKey , user );
}
// Process with cached user data
await processEventWithUser ( event , user );
}
// Cache webhook configurations
async function getWebhookConfig ( organisationId ) {
const cacheKey = `webhook_config_ ${ organisationId } ` ;
let config = cache . get ( cacheKey );
if ( ! config ) {
config = await fetchWebhookConfig ( organisationId );
cache . set ( cacheKey , config , 600 ); // Cache for 10 minutes
}
return config ;
}
Testing and Validation
Integration Testing
const request = require ( 'supertest' );
const app = require ( '../webhook-server' );
describe ( 'Webhook Integration Tests' , () => {
it ( 'should process challenge completion webhook' , async () => {
const payload = {
id: 'test-webhook-123' ,
eventCategory: 'challenge' ,
eventSubCategory: 'challenge-completion' ,
userId: 'test-user-123' ,
payload: {
challengeName: 'Test Challenge' ,
pointsEarned: 100
}
};
const response = await request ( app )
. post ( '/webhooks/nudj' )
. set ( 'Authorization' , `Bearer ${ process . env . WEBHOOK_SECRET } ` )
. set ( 'X-Webhook-Source' , 'nudj-platform' )
. send ( payload )
. expect ( 200 );
// Verify processing occurred
const user = await getUserFromDatabase ( 'test-user-123' );
expect ( user . totalPointsEarned ). toBeGreaterThan ( 0 );
});
it ( 'should reject invalid signatures' , async () => {
const payload = { id: 'test-webhook-invalid' };
await request ( app )
. post ( '/webhooks/nudj' )
. set ( 'Authorization' , 'Bearer invalid-secret' )
. send ( payload )
. expect ( 401 );
});
it ( 'should handle duplicate webhooks' , async () => {
const payload = {
id: 'duplicate-test-webhook' ,
eventSubCategory: 'test-event'
};
// Send same webhook twice
await request ( app )
. post ( '/webhooks/nudj' )
. set ( 'Authorization' , `Bearer ${ process . env . WEBHOOK_SECRET } ` )
. set ( 'X-Webhook-Source' , 'nudj-platform' )
. send ( payload )
. expect ( 200 );
await request ( app )
. post ( '/webhooks/nudj' )
. set ( 'Authorization' , `Bearer ${ process . env . WEBHOOK_SECRET } ` )
. set ( 'X-Webhook-Source' , 'nudj-platform' )
. send ( payload )
. expect ( 200 );
// Verify processing only happened once
// Add your verification logic here
});
});
Common Solutions
Network and Connectivity
Ensure your firewall allows incoming HTTPS traffic: # Check if port 443 is open
sudo ufw status
sudo ufw allow 443
# For development (port 3000)
sudo ufw allow 3000
Load Balancer Configuration
Configure your load balancer for webhook traffic:
Enable session affinity if needed
Set appropriate timeout values (>10 seconds)
Configure health checks
Ensure SSL termination is properly configured
Verify your domain configuration: # Check DNS resolution
nslookup your-webhook-domain.com
# Test SSL certificate
openssl s_client -connect your-webhook-domain.com:443
# Verify certificate chain
curl -I https://your-webhook-domain.com/webhooks/nudj
Getting Help
Check the Logs Start by examining your application logs and webhook processing logs for error patterns.
Test Locally Use ngrok or similar tools to test webhooks against your local development environment.
Verify Configuration Double-check your webhook configuration using the Admin API endpoints.
Contact Support Reach out to support@nudj.cx with specific error messages and webhook IDs.
When contacting support, please include:
Webhook configuration (without secrets)
Specific error messages and stack traces
Webhook IDs that failed
Timeline of when issues started
Your endpoint URL (for connectivity testing)
Debugging Checklist
Verify Webhook Configuration
Check Endpoint Implementation
Test Network Connectivity
Still having issues? Check our Implementation Guide or review the Event Catalog to ensure you’re handling webhooks correctly.