Skip to main content

Overview

This guide walks you through implementing webhook endpoints to receive real-time notifications from the Nudj platform. You’ll learn how to configure webhooks, handle incoming requests, and implement best practices for production systems.

Prerequisites

Admin API Access

You need Admin API credentials to configure webhooks

HTTPS Endpoint

Webhook URLs must use HTTPS in production

Event Processing

A system to handle and process incoming events

Authentication

Mechanism to verify webhook authenticity

Step 1: Configure Your Webhook

First, use the Admin API to configure your webhook endpoint:
curl -X PUT "https://{subdomain}.nudj.cx/api/v2/admin/webhooks/configs" \
  -H "Content-Type: application/json" \
  -H "x-api-token: YOUR_ADMIN_API_TOKEN" \
  -d '[
    {
      "events": [
        "challenge-completion",
        "achievement-completion",
        "reward-claim"
      ],
      "url": "https://your-app.com/webhooks/nudj",
      "httpMethod": "POST",
      "isEnabled": true,
      "maxRetries": 3,
      "headers": {
        "Authorization": "Bearer your-webhook-secret",
        "X-Webhook-Source": "nudj-platform"
      }
    }
  ]'

Step 2: Implement Webhook Endpoint

Create an endpoint to receive and process webhook events:
const express = require('express');
const crypto = require('crypto');
const app = express();

// Middleware to parse JSON with raw body for signature verification
app.use('/webhooks', express.json({
  verify: (req, res, buf, encoding) => {
    req.rawBody = buf;
  }
}));

// Webhook endpoint
app.post('/webhooks/nudj', async (req, res) => {
  try {
    // Verify the webhook is from Nudj
    if (!verifyWebhookSignature(req)) {
      return res.status(401).send('Unauthorized');
    }

    const event = req.body;
    console.log('Received webhook:', event.eventSubCategory);

    // Process the event
    await processWebhookEvent(event);

    // Always respond with 200 for successful processing
    res.status(200).send('OK');
  } catch (error) {
    console.error('Webhook processing error:', error);
    res.status(500).send('Internal Server Error');
  }
});

function verifyWebhookSignature(req) {
  const expectedAuth = `Bearer ${process.env.WEBHOOK_SECRET}`;
  const receivedAuth = req.get('Authorization');
  const webhookSource = req.get('X-Webhook-Source');
  
  return receivedAuth === expectedAuth && webhookSource === 'nudj-platform';
}

async function processWebhookEvent(event) {
  switch (event.eventSubCategory) {
    case 'challenge-completion':
      await handleChallengeCompletion(event);
      break;
    case 'achievement-completion':
      await handleAchievementCompletion(event);
      break;
    case 'reward-claim':
      await handleRewardClaim(event);
      break;
    default:
      console.log('Unhandled event type:', event.eventSubCategory);
  }
}

async function handleChallengeCompletion(event) {
  const { userId, eventSourceId, payload } = event;
  
  // Example: Send congratulatory email
  await sendEmail(userId, {
    subject: 'Challenge Completed!',
    template: 'challenge-completion',
    data: {
      challengeName: payload.challengeName,
      pointsEarned: payload.pointsEarned
    }
  });
  
  // Example: Update external system
  await updateExternalProfile(userId, {
    lastChallengeCompleted: eventSourceId,
    totalChallengesCompleted: await getChallengeCount(userId) + 1
  });
}

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

Step 3: Implement Event Handlers

Create specific handlers for different event types:
async function handleChallengeCompletion(event) {
  const { userId, communityId, eventSourceId, payload } = event;
  const challengeId = eventSourceId;
  
  try {
    // 1. Award bonus points for fast completion
    if (payload.timeToComplete < 3600) { // Less than 1 hour
      await awardBonusPoints(userId, 50, 'Fast challenge completion');
    }
    
    // 2. Update user statistics
    await updateUserStats(userId, {
      challengesCompleted: 1,
      totalPointsEarned: payload.pointsEarned,
      totalXpEarned: payload.xpEarned
    });
    
    // 3. Check for achievement eligibility
    const challengeCount = await getUserChallengeCount(userId);
    if (challengeCount === 10) {
      await triggerAchievement(userId, 'challenge-master');
    }
    
    // 4. Send personalized notification
    await sendNotification(userId, {
      type: 'challenge_completed',
      title: 'Challenge Complete!',
      message: `Great job completing "${payload.challengeName}"!`,
      data: {
        pointsEarned: payload.pointsEarned,
        xpEarned: payload.xpEarned,
        completionTime: formatDuration(payload.timeToComplete)
      }
    });
    
    // 5. Update leaderboards
    await updateLeaderboard(communityId, userId, {
      challengesCompleted: 1,
      pointsEarned: payload.pointsEarned
    });
    
  } catch (error) {
    console.error(`Error handling challenge completion for user ${userId}:`, error);
    throw error;
  }
}

Step 4: Error Handling and Retry Logic

Implement robust error handling to ensure reliable webhook processing:
const MAX_RETRIES = 3;
const RETRY_DELAY = 1000; // 1 second

async function processWebhookEvent(event) {
  let attempt = 1;
  
  while (attempt <= MAX_RETRIES) {
    try {
      await processEvent(event);
      return; // Success, exit retry loop
    } catch (error) {
      console.error(`Attempt ${attempt} failed:`, error);
      
      if (attempt === MAX_RETRIES) {
        // Final attempt failed, handle accordingly
        await handleFinalFailure(event, error);
        throw error;
      }
      
      // Wait before retrying
      await sleep(RETRY_DELAY * attempt);
      attempt++;
    }
  }
}

async function handleFinalFailure(event, error) {
  // Log to error monitoring service
  console.error('Webhook processing failed after all retries:', {
    eventId: event.id,
    eventType: event.eventSubCategory,
    error: error.message,
    stack: error.stack
  });
  
  // Store in dead letter queue for manual review
  await storeInDeadLetterQueue(event, error);
  
  // Alert operations team
  await sendAlert('webhook-processing-failed', {
    eventId: event.id,
    eventType: event.eventSubCategory,
    error: error.message
  });
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Step 5: Testing Your Implementation

Test your webhook implementation thoroughly:
const axios = require('axios');

// Test webhook endpoint locally
async function testWebhook() {
  const testPayload = {
    id: 'test-webhook-' + Date.now(),
    eventCategory: 'challenge',
    eventSubCategory: 'challenge-completion',
    organisationId: '60f7b8c8e7d4a2b1c9e3f4a1',
    communityId: '60f7b8c8e7d4a2b1c9e3f4a2',
    userId: '60f7b8c8e7d4a2b1c9e3f4a3',
    eventSourceId: '60f7b8c8e7d4a2b1c9e3f4a4',
    payload: {
      challengeId: '60f7b8c8e7d4a2b1c9e3f4a4',
      challengeName: 'Test Challenge',
      completedAt: new Date().toISOString(),
      timeToComplete: 1800,
      pointsEarned: 100,
      xpEarned: 50
    },
    createdAt: new Date().toISOString()
  };

  try {
    const response = await axios.post('http://localhost:3000/webhooks/nudj', testPayload, {
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.WEBHOOK_SECRET}`,
        'X-Webhook-Source': 'nudj-platform'
      }
    });
    
    console.log('Test webhook successful:', response.status);
  } catch (error) {
    console.error('Test webhook failed:', error.message);
  }
}

testWebhook();

Step 6: Production Deployment

Security Checklist

  • Use valid SSL certificates
  • Enable HSTS headers
  • Disable HTTP fallback
  • Use strong cipher suites
  • Verify webhook signatures
  • Use environment variables for secrets
  • Implement rate limiting
  • Add IP allowlisting if needed
  • Implement retry logic
  • Use dead letter queues
  • Add comprehensive logging
  • Set up monitoring and alerts
  • Process webhooks asynchronously
  • Implement connection pooling
  • Add caching where appropriate
  • Monitor response times

Monitoring and Logging

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: 'webhook-handler' },
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
    new winston.transports.Console({
      format: winston.format.simple()
    })
  ]
});

app.post('/webhooks/nudj', async (req, res) => {
  const startTime = Date.now();
  const event = req.body;
  
  logger.info('Webhook received', {
    webhookId: event.id,
    eventType: event.eventSubCategory,
    userId: event.userId,
    timestamp: event.createdAt
  });
  
  try {
    await processWebhookEvent(event);
    
    logger.info('Webhook processed successfully', {
      webhookId: event.id,
      processingTime: Date.now() - startTime
    });
    
    res.status(200).send('OK');
  } catch (error) {
    logger.error('Webhook processing failed', {
      webhookId: event.id,
      error: error.message,
      stack: error.stack,
      processingTime: Date.now() - startTime
    });
    
    res.status(500).send('Internal Server Error');
  }
});

Common Integration Patterns

Real-time Notifications

async function sendPushNotification(userId, event) {
  const userDevices = await getUserDevices(userId);
  
  const notification = {
    title: getNotificationTitle(event),
    body: getNotificationMessage(event),
    data: {
      eventType: event.eventSubCategory,
      eventId: event.id,
      deepLink: generateDeepLink(event)
    }
  };
  
  for (const device of userDevices) {
    await pushService.send(device.token, notification);
  }
}

function getNotificationTitle(event) {
  const titles = {
    'challenge-completion': 'Challenge Complete! 🏆',
    'achievement-completion': 'Achievement Unlocked! 🎉',
    'reward-claim': 'Reward Claimed! 🎁',
    'streak-extended': 'Streak Extended! 🔥'
  };
  
  return titles[event.eventSubCategory] || 'Nudj Notification';
}

Email Marketing Integration

async function triggerEmailCampaign(event) {
  const user = await getUser(event.userId);
  
  const campaignData = {
    email: user.email,
    templateId: getEmailTemplate(event.eventSubCategory),
    personalizations: [{
      email: user.email,
      dynamic_template_data: {
        firstName: user.firstName,
        eventData: event.payload,
        communityName: await getCommunityName(event.communityId)
      }
    }]
  };
  
  await emailService.send(campaignData);
}

function getEmailTemplate(eventType) {
  const templates = {
    'challenge-completion': 'challenge_completed_v2',
    'achievement-completion': 'achievement_unlocked_v2',
    'reward-claim': 'reward_claimed_v1'
  };
  
  return templates[eventType] || 'generic_notification_v1';
}

Analytics Integration

async function trackAnalyticsEvent(event) {
  const analyticsEvent = {
    userId: event.userId,
    eventName: `nudj_${event.eventSubCategory}`,
    properties: {
      organisationId: event.organisationId,
      communityId: event.communityId,
      eventSourceId: event.eventSourceId,
      timestamp: event.createdAt,
      ...event.payload
    }
  };
  
  // Send to multiple analytics platforms
  await Promise.all([
    analytics.track(analyticsEvent),
    mixpanel.track(analyticsEvent.userId, analyticsEvent.eventName, analyticsEvent.properties),
    amplitude.logEvent(analyticsEvent)
  ]);
}

Best Practices

Response Quickly

Return HTTP 200 within 10 seconds. Process heavy workloads asynchronously.

Handle Duplicates

Use webhook IDs to implement idempotent processing and prevent duplicate handling.

Validate Payloads

Always verify webhook authenticity and validate payload structure before processing.

Monitor Performance

Track processing times, error rates, and webhook delivery success rates.

Implement Retries

Handle transient failures gracefully with exponential backoff retry logic.

Log Everything

Maintain detailed logs for debugging and monitoring webhook processing.

Next Steps

Event Catalog

See every available webhook event and its specific payload.

Testing Guide

Learn how to trigger test events to verify your endpoint.