> ## 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.

# Troubleshooting Guide

> Common webhook issues, debugging techniques, and solutions for reliable webhook processing

## Common Issues and Solutions

### Webhook Not Being Received

<AccordionGroup>
  <Accordion title="Check Webhook Configuration">
    Verify your webhook is properly configured and enabled:

    ```bash theme={null}
    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
  </Accordion>

  <Accordion title="Verify Endpoint Accessibility">
    Test if your webhook endpoint is reachable:

    ```bash theme={null}
    # 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
  </Accordion>

  <Accordion title="Check Event Triggers">
    Ensure the events you're expecting are actually occurring:

    ```javascript theme={null}
    // 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
  </Accordion>
</AccordionGroup>

### Authentication Failures

<AccordionGroup>
  <Accordion title="Header Authentication Issues">
    Nudj sends custom headers that your endpoint must verify:

    ```javascript theme={null}
    // Correct way to verify webhook authenticity
    function verifyWebhookSignature(req) {
      const expectedAuth = `Bearer ${process.env.WEBHOOK_SECRET}`;
      const receivedAuth = req.get('Authorization');
      const webhookSource = req.get('X-Webhook-Source');
      
      if (receivedAuth !== expectedAuth) {
        console.error('Auth header mismatch:', { expected: expectedAuth, received: receivedAuth });
        return false;
      }
      
      if (webhookSource !== 'nudj-platform') {
        console.error('Invalid webhook source:', webhookSource);
        return false;
      }
      
      return true;
    }
    ```

    **Common mistakes:**

    * Case sensitivity in header names
    * Missing `Bearer ` prefix
    * Wrong environment variable
    * Headers not configured in webhook settings
  </Accordion>

  <Accordion title="Environment Variable Issues">
    Ensure your secrets are properly configured:

    ```javascript theme={null}
    // 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
  </Accordion>
</AccordionGroup>

### Processing Errors

<AccordionGroup>
  <Accordion title="Timeout Issues">
    Webhooks must respond within 10 seconds:

    ```javascript theme={null}
    // 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');
      }
    });
    ```
  </Accordion>

  <Accordion title="Memory and Performance Issues">
    Optimize webhook processing for high throughput:

    ```javascript theme={null}
    // Use connection pooling
    const pool = new Pool({
      connectionString: process.env.DATABASE_URL,
      max: 20
    });

    // Implement rate limiting
    const rateLimit = require('express-rate-limit');
    const webhookLimiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 1000, // limit each IP to 1000 requests per windowMs
      message: 'Too many webhook requests'
    });

    app.use('/webhooks', webhookLimiter);
    ```
  </Accordion>

  <Accordion title="JSON Parsing Errors">
    Handle malformed webhook payloads gracefully:

    ```javascript theme={null}
    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');
        }
      }
    }));
    ```
  </Accordion>
</AccordionGroup>

## Debugging Techniques

### Enable Detailed Logging

<CodeGroup>
  ```javascript Comprehensive Logging theme={null}
  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');
    }
  });
  ```
</CodeGroup>

### Request Inspection Tools

<CodeGroup>
  ```javascript Request Inspector Middleware theme={null}
  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);
  }
  ```
</CodeGroup>

### Webhook Testing Tools

<CodeGroup>
  ```javascript Local Testing Setup theme={null}
  // 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}`);
  });
  ```

  ```bash Testing Script theme={null}
  #!/bin/bash
  # test-webhook.sh

  WEBHOOK_URL="https://your-app.com/webhooks/nudj"
  SECRET="your-webhook-secret"

  # Test payload
  payload='{
    "id": "test-'$(date +%s)'",
    "eventCategory": "challenge",
    "eventSubCategory": "challenge-completion",
    "organisationId": "test-org-id",
    "communityId": "test-community-id",
    "userId": "test-user-id",
    "eventSourceId": "test-challenge-id",
    "payload": {
      "challengeName": "Test Challenge",
      "pointsEarned": 100
    },
    "createdAt": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"
  }'

  echo "Testing webhook endpoint..."
  echo "URL: $WEBHOOK_URL"
  echo "Payload: $payload"

  response=$(curl -s -w "\n%{http_code}" -X POST "$WEBHOOK_URL" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $SECRET" \
    -H "X-Webhook-Source: nudj-platform" \
    -d "$payload")

  echo "Response: $response"
  ```
</CodeGroup>

## Error Monitoring and Alerting

### Webhook Health Checks

<CodeGroup>
  ```javascript Health Monitoring theme={null}
  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);
  });
  ```
</CodeGroup>

### Error Notifications

<CodeGroup>
  ```javascript Alert System theme={null}
  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();
  ```
</CodeGroup>

## Performance Optimization

### Async Processing

<CodeGroup>
  ```javascript Queue-Based Processing theme={null}
  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
    }
  });
  ```
</CodeGroup>

### Caching Strategies

<CodeGroup>
  ```javascript Smart Caching theme={null}
  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;
  }
  ```
</CodeGroup>

## Testing and Validation

### Integration Testing

<CodeGroup>
  ```javascript Integration Tests theme={null}
  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
    });
  });
  ```
</CodeGroup>

## Common Solutions

### Network and Connectivity

<AccordionGroup>
  <Accordion title="Firewall Configuration">
    Ensure your firewall allows incoming HTTPS traffic:

    ```bash theme={null}
    # Check if port 443 is open
    sudo ufw status
    sudo ufw allow 443

    # For development (port 3000)
    sudo ufw allow 3000
    ```
  </Accordion>

  <Accordion title="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
  </Accordion>

  <Accordion title="DNS and SSL Issues">
    Verify your domain configuration:

    ```bash theme={null}
    # 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
    ```
  </Accordion>
</AccordionGroup>

## Getting Help

<CardGroup cols={2}>
  <Card title="Check the Logs" icon="file-text">
    Start by examining your application logs and webhook processing logs for error patterns.
  </Card>

  <Card title="Test Locally" icon="laptop">
    Use ngrok or similar tools to test webhooks against your local development environment.
  </Card>

  <Card title="Verify Configuration" icon="settings">
    Double-check your webhook configuration using the Admin API endpoints.
  </Card>

  <Card title="Contact Support" icon="life-ring">
    Reach out to [support@nudj.cx](mailto:support@nudj.cx) with specific error messages and webhook IDs.
  </Card>
</CardGroup>

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

<Steps>
  <Step title="Verify Webhook Configuration">
    * [ ] Webhook is enabled (`isEnabled: true`)
    * [ ] Events array contains expected event types
    * [ ] URL is valid and reachable
    * [ ] HTTP method is appropriate
    * [ ] Headers are correctly configured
  </Step>

  <Step title="Check Endpoint Implementation">
    * [ ] Endpoint responds within 10 seconds
    * [ ] Returns HTTP 2xx for successful processing
    * [ ] Properly verifies webhook signatures
    * [ ] Handles JSON parsing errors
    * [ ] Implements idempotency
  </Step>

  <Step title="Test Network Connectivity">
    * [ ] Endpoint is accessible from external networks
    * [ ] SSL certificate is valid
    * [ ] Firewall allows HTTPS traffic
    * [ ] DNS resolves correctly
  </Step>

  <Step title="Monitor and Log">
    * [ ] Comprehensive logging is enabled
    * [ ] Error monitoring is configured
    * [ ] Health checks are implemented
    * [ ] Metrics are being tracked
  </Step>
</Steps>

Still having issues? Check our [Implementation Guide](/developer/webhooks/implementation) or review the [Event Catalog](/developer/webhooks/event-catalog) to ensure you're handling webhooks correctly.
