Recipe Library Overview
Pre-built webhook integration patterns for common use cases. Each recipe includes complete code examples and deployment instructions.Recipe: Slack Notification Bot
Send real-time Slack notifications when users complete challenges or earn rewards.- Setup
- Node.js
- Python
Prerequisites
- Slack workspace with incoming webhooks enabled
- Slack webhook URL from your workspace settings
Configuration
- Get your Slack webhook URL from Slack App settings
- Configure Nudj webhook to point to your handler
- Deploy the handler code below
const express = require('express');
const axios = require('axios');
const app = express();
const SLACK_WEBHOOK_URL = process.env.SLACK_WEBHOOK_URL;
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
app.post('/webhooks/nudj', express.json(), async (req, res) => {
// Verify webhook authenticity
if (req.headers['x-api-key'] !== WEBHOOK_SECRET) {
return res.status(401).json({ error: 'Unauthorized' });
}
const event = req.body;
try {
// Handle different event types
switch (event.eventSubCategory) {
case 'challenge-completion':
await sendSlackMessage({
text: `🎯 Challenge Completed!`,
blocks: [{
type: 'section',
text: {
type: 'mrkdwn',
text: `*User:* ${event.userId}\n*Challenge:* ${event.eventSourceId}\n*Time:* ${event.createdAt}`
}
}]
});
break;
case 'reward-distribution':
const { earnedPoints, earnedXp } = event.payload;
await sendSlackMessage({
text: `🎁 Rewards Distributed!`,
blocks: [{
type: 'section',
text: {
type: 'mrkdwn',
text: `*User:* ${event.userId}\n*Points:* ${earnedPoints}\n*XP:* ${earnedXp}`
}
}]
});
break;
case 'achievement-completion':
await sendSlackMessage({
text: `🏆 Achievement Unlocked!`,
blocks: [{
type: 'section',
text: {
type: 'mrkdwn',
text: `*User:* ${event.userId}\n*Achievement:* ${event.payload.achievementName}`
}
}]
});
break;
}
res.status(200).json({ success: true });
} catch (error) {
console.error('Slack notification failed:', error);
res.status(200).json({ success: true }); // Return 200 to prevent retries
}
});
async function sendSlackMessage(message) {
await axios.post(SLACK_WEBHOOK_URL, message);
}
app.listen(3000, () => {
console.log('Webhook handler running on port 3000');
});
from flask import Flask, request, jsonify
import requests
import os
from datetime import datetime
app = Flask(__name__)
SLACK_WEBHOOK_URL = os.environ.get('SLACK_WEBHOOK_URL')
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET')
@app.route('/webhooks/nudj', methods=['POST'])
def handle_webhook():
# Verify webhook authenticity
if request.headers.get('X-Api-Key') != WEBHOOK_SECRET:
return jsonify({'error': 'Unauthorized'}), 401
event = request.json
try:
event_type = event.get('eventSubCategory')
if event_type == 'challenge-completion':
send_slack_message({
'text': '🎯 Challenge Completed!',
'blocks': [{
'type': 'section',
'text': {
'type': 'mrkdwn',
'text': f"*User:* {event.get('userId')}\n"
f"*Challenge:* {event.get('eventSourceId')}\n"
f"*Time:* {event.get('createdAt')}"
}
}]
})
elif event_type == 'reward-distribution':
payload = event.get('payload', {})
send_slack_message({
'text': '🎁 Rewards Distributed!',
'blocks': [{
'type': 'section',
'text': {
'type': 'mrkdwn',
'text': f"*User:* {event.get('userId')}\n"
f"*Points:* {payload.get('earnedPoints')}\n"
f"*XP:* {payload.get('earnedXp')}"
}
}]
})
return jsonify({'success': True}), 200
except Exception as e:
print(f"Error processing webhook: {e}")
return jsonify({'success': True}), 200 # Return 200 to prevent retries
def send_slack_message(message):
requests.post(SLACK_WEBHOOK_URL, json=message)
if __name__ == '__main__':
app.run(port=3000)
Recipe: Email Notification System
Send personalized emails for important user events using SendGrid.- Setup
- Node.js
- Python
Prerequisites
- SendGrid account and API key
- Email templates configured in SendGrid
- Verified sender domain
Email Templates Needed
- Challenge completion
- Reward earned
- Achievement unlocked
- Weekly summary
const express = require('express');
const sgMail = require('@sendgrid/mail');
const app = express();
sgMail.setApiKey(process.env.SENDGRID_API_KEY);
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
// Template IDs from SendGrid
const TEMPLATES = {
'challenge-completion': 'd-challenge-complete-template-id',
'reward-distribution': 'd-reward-earned-template-id',
'achievement-completion': 'd-achievement-template-id',
'streak-extended': 'd-streak-template-id'
};
app.post('/webhooks/nudj', express.json(), async (req, res) => {
if (req.headers['x-api-key'] !== WEBHOOK_SECRET) {
return res.status(401).json({ error: 'Unauthorized' });
}
const event = req.body;
try {
const templateId = TEMPLATES[event.eventSubCategory];
if (!templateId) {
return res.status(200).json({ success: true });
}
// Extract user email from payload (reward-distribution includes it)
let userEmail;
if (event.eventSubCategory === 'reward-distribution') {
userEmail = event.payload?.user?.email;
} else {
// You'd need to look up user email from your database
userEmail = await getUserEmail(event.userId);
}
if (userEmail) {
await sendEmail(userEmail, templateId, event);
}
res.status(200).json({ success: true });
} catch (error) {
console.error('Email send failed:', error);
res.status(200).json({ success: true });
}
});
async function sendEmail(to, templateId, eventData) {
const msg = {
to,
from: 'notifications@yourcompany.com',
templateId,
dynamicTemplateData: {
userId: eventData.userId,
eventType: eventData.eventSubCategory,
timestamp: eventData.createdAt,
...eventData.payload
}
};
await sgMail.send(msg);
}
async function getUserEmail(userId) {
// Implement your user lookup logic here
// This would query your database or call your API
return null; // Placeholder
}
app.listen(3000, () => {
console.log('Email webhook handler running on port 3000');
});
from flask import Flask, request, jsonify
from sendgrid import SendGridAPIClient
from sendgrid.helpers.mail import Mail, Email, To
import os
app = Flask(__name__)
sg = SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY'))
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET')
TEMPLATES = {
'challenge-completion': 'd-challenge-complete-template-id',
'reward-distribution': 'd-reward-earned-template-id',
'achievement-completion': 'd-achievement-template-id',
'streak-extended': 'd-streak-template-id'
}
@app.route('/webhooks/nudj', methods=['POST'])
def handle_webhook():
if request.headers.get('X-Api-Key') != WEBHOOK_SECRET:
return jsonify({'error': 'Unauthorized'}), 401
event = request.json
try:
template_id = TEMPLATES.get(event.get('eventSubCategory'))
if not template_id:
return jsonify({'success': True}), 200
# Get user email
user_email = None
if event.get('eventSubCategory') == 'reward-distribution':
user_email = event.get('payload', {}).get('user', {}).get('email')
else:
user_email = get_user_email(event.get('userId'))
if user_email:
send_email(user_email, template_id, event)
return jsonify({'success': True}), 200
except Exception as e:
print(f"Error: {e}")
return jsonify({'success': True}), 200
def send_email(to_email, template_id, event_data):
message = Mail(
from_email='notifications@yourcompany.com',
to_emails=to_email
)
message.template_id = template_id
message.dynamic_template_data = {
'userId': event_data.get('userId'),
'eventType': event_data.get('eventSubCategory'),
'timestamp': event_data.get('createdAt'),
**event_data.get('payload', {})
}
sg.send(message)
def get_user_email(user_id):
# Implement your user lookup
return None
if __name__ == '__main__':
app.run(port=3000)
Recipe: Analytics & Data Pipeline
Stream events to your data warehouse for analytics.- Setup
- BigQuery Integration
- Snowflake Integration
Prerequisites
- Data warehouse (BigQuery, Snowflake, Redshift)
- Message queue (optional but recommended)
Architecture
- Webhook receives events
- Events queued for processing
- Batch insert to data warehouse
- Analytics dashboards update
const express = require('express');
const { BigQuery } = require('@google-cloud/bigquery');
const app = express();
const bigquery = new BigQuery();
const dataset = bigquery.dataset('nudj_events');
const table = dataset.table('webhook_events');
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
const eventBatch = [];
const BATCH_SIZE = 100;
const FLUSH_INTERVAL = 5000; // 5 seconds
app.post('/webhooks/nudj', express.json(), async (req, res) => {
if (req.headers['x-api-key'] !== WEBHOOK_SECRET) {
return res.status(401).json({ error: 'Unauthorized' });
}
const event = req.body;
// Add event to batch
eventBatch.push({
event_id: event.id,
event_category: event.eventCategory,
event_subcategory: event.eventSubCategory,
organisation_id: event.organisationId,
community_id: event.communityId,
user_id: event.userId,
event_source_id: event.eventSourceId,
payload: JSON.stringify(event.payload),
tags: event.tags,
created_at: event.createdAt,
received_at: new Date().toISOString()
});
// Flush batch if it's full
if (eventBatch.length >= BATCH_SIZE) {
await flushBatch();
}
res.status(200).json({ success: true });
});
async function flushBatch() {
if (eventBatch.length === 0) return;
const events = [...eventBatch];
eventBatch.length = 0; // Clear batch
try {
await table.insert(events);
console.log(`Inserted ${events.length} events to BigQuery`);
} catch (error) {
console.error('BigQuery insert failed:', error);
// Consider adding to dead letter queue
}
}
// Periodic flush
setInterval(flushBatch, FLUSH_INTERVAL);
app.listen(3000, () => {
console.log('Analytics webhook handler running on port 3000');
});
from flask import Flask, request, jsonify
import snowflake.connector
import json
import os
from datetime import datetime
from queue import Queue
import threading
app = Flask(__name__)
WEBHOOK_SECRET = os.environ.get('WEBHOOK_SECRET')
event_queue = Queue()
# Snowflake connection
conn = snowflake.connector.connect(
user=os.environ.get('SNOWFLAKE_USER'),
password=os.environ.get('SNOWFLAKE_PASSWORD'),
account=os.environ.get('SNOWFLAKE_ACCOUNT'),
warehouse='COMPUTE_WH',
database='NUDJ_ANALYTICS',
schema='EVENTS'
)
@app.route('/webhooks/nudj', methods=['POST'])
def handle_webhook():
if request.headers.get('X-Api-Key') != WEBHOOK_SECRET:
return jsonify({'error': 'Unauthorized'}), 401
event = request.json
# Queue event for processing
event_queue.put({
'EVENT_ID': event.get('id'),
'EVENT_CATEGORY': event.get('eventCategory'),
'EVENT_SUBCATEGORY': event.get('eventSubCategory'),
'ORGANISATION_ID': event.get('organisationId'),
'COMMUNITY_ID': event.get('communityId'),
'USER_ID': event.get('userId'),
'EVENT_SOURCE_ID': event.get('eventSourceId'),
'PAYLOAD': json.dumps(event.get('payload', {})),
'TAGS': json.dumps(event.get('tags', [])),
'CREATED_AT': event.get('createdAt'),
'RECEIVED_AT': datetime.utcnow().isoformat()
})
return jsonify({'success': True}), 200
def process_queue():
"""Background worker to process queued events"""
cursor = conn.cursor()
batch = []
while True:
try:
# Collect batch
while not event_queue.empty() and len(batch) < 100:
batch.append(event_queue.get())
if batch:
# Insert batch into Snowflake
cursor.executemany(
"""INSERT INTO WEBHOOK_EVENTS
(EVENT_ID, EVENT_CATEGORY, EVENT_SUBCATEGORY,
ORGANISATION_ID, COMMUNITY_ID, USER_ID,
EVENT_SOURCE_ID, PAYLOAD, TAGS,
CREATED_AT, RECEIVED_AT)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)""",
[tuple(event.values()) for event in batch]
)
conn.commit()
print(f"Inserted {len(batch)} events to Snowflake")
batch.clear()
except Exception as e:
print(f"Error processing batch: {e}")
threading.Event().wait(5) # Process every 5 seconds
# Start background worker
worker = threading.Thread(target=process_queue, daemon=True)
worker.start()
if __name__ == '__main__':
app.run(port=3000)
Recipe: Zapier/Make.com Integration
Connect Nudj webhooks to 1000+ apps without code.- Zapier Setup
- Make.com Setup
Creating a Zap
-
Trigger Setup
- Choose “Webhooks by Zapier” as trigger
- Select “Catch Hook” as event
- Copy the webhook URL provided
-
Configure Nudj Webhook
{ "url": "https://hooks.zapier.com/hooks/catch/YOUR_ID/", "events": ["challenge-completion", "reward-distribution"], "httpMethod": "POST", "isEnabled": true } -
Test & Map Fields
- Send test webhook from Nudj
- Map fields to your action apps
- Common actions:
- Add row to Google Sheets
- Create Salesforce lead
- Send Teams message
- Create Asana task
Creating a Scenario
- Webhook Module
- Add “Webhooks” → “Custom webhook”
- Copy the webhook URL
- Add to Nudj configuration
- Data Processing
- Add Router for different event types
- Parse JSON payload
- Add filters for specific conditions
- Action Modules
- Google Sheets: Log events
- Slack: Send notifications
- Email: Send alerts
- Database: Store records
Example Scenario
Webhook → Router → Branch 1: Challenge Complete → Slack
→ Branch 2: Reward Earned → Google Sheets
→ Branch 3: Achievement → Email
Recipe: Database Sync
Keep your database synchronized with Nudj events.- PostgreSQL
- MongoDB
const express = require('express');
const { Pool } = require('pg');
const app = express();
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
// Create events table if not exists
async function initDatabase() {
await pool.query(`
CREATE TABLE IF NOT EXISTS nudj_events (
id VARCHAR(255) PRIMARY KEY,
event_category VARCHAR(100),
event_subcategory VARCHAR(100),
organisation_id VARCHAR(255),
community_id VARCHAR(255),
user_id VARCHAR(255),
event_source_id VARCHAR(255),
payload JSONB,
tags TEXT[],
created_at TIMESTAMP,
received_at TIMESTAMP DEFAULT NOW()
)
`);
// Create user points table
await pool.query(`
CREATE TABLE IF NOT EXISTS user_points (
user_id VARCHAR(255) PRIMARY KEY,
total_points INTEGER DEFAULT 0,
total_xp INTEGER DEFAULT 0,
last_updated TIMESTAMP DEFAULT NOW()
)
`);
}
app.post('/webhooks/nudj', express.json(), async (req, res) => {
if (req.headers['x-api-key'] !== WEBHOOK_SECRET) {
return res.status(401).json({ error: 'Unauthorized' });
}
const event = req.body;
try {
// Store raw event
await pool.query(
`INSERT INTO nudj_events
(id, event_category, event_subcategory, organisation_id,
community_id, user_id, event_source_id, payload, tags, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
ON CONFLICT (id) DO NOTHING`,
[
event.id,
event.eventCategory,
event.eventSubCategory,
event.organisationId,
event.communityId,
event.userId,
event.eventSourceId,
JSON.stringify(event.payload),
event.tags,
event.createdAt
]
);
// Update user points for reward events
if (event.eventSubCategory === 'reward-distribution') {
const { earnedPoints = 0, earnedXp = 0 } = event.payload;
await pool.query(
`INSERT INTO user_points (user_id, total_points, total_xp)
VALUES ($1, $2, $3)
ON CONFLICT (user_id)
DO UPDATE SET
total_points = user_points.total_points + $2,
total_xp = user_points.total_xp + $3,
last_updated = NOW()`,
[event.userId, earnedPoints, earnedXp]
);
}
res.status(200).json({ success: true });
} catch (error) {
console.error('Database error:', error);
res.status(500).json({ error: 'Internal error' });
}
});
initDatabase().then(() => {
app.listen(3000, () => {
console.log('Database sync webhook running on port 3000');
});
});
const express = require('express');
const { MongoClient } = require('mongodb');
const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
const MONGODB_URI = process.env.MONGODB_URI;
let db;
MongoClient.connect(MONGODB_URI).then(client => {
db = client.db('nudj_analytics');
console.log('Connected to MongoDB');
});
app.post('/webhooks/nudj', express.json(), async (req, res) => {
if (req.headers['x-api-key'] !== WEBHOOK_SECRET) {
return res.status(401).json({ error: 'Unauthorized' });
}
const event = req.body;
try {
// Store event
await db.collection('events').insertOne({
...event,
_id: event.id,
receivedAt: new Date()
});
// Update user statistics
if (event.eventSubCategory === 'reward-distribution') {
const { earnedPoints = 0, earnedXp = 0 } = event.payload;
await db.collection('user_stats').updateOne(
{ _id: event.userId },
{
$inc: {
totalPoints: earnedPoints,
totalXp: earnedXp,
rewardCount: 1
},
$set: { lastActivity: new Date() },
$setOnInsert: {
userId: event.userId,
createdAt: new Date()
}
},
{ upsert: true }
);
}
// Track challenge completions
if (event.eventSubCategory === 'challenge-completion') {
await db.collection('challenge_stats').updateOne(
{ _id: event.eventSourceId },
{
$inc: { completions: 1 },
$addToSet: { completedBy: event.userId }
},
{ upsert: true }
);
}
res.status(200).json({ success: true });
} catch (error) {
console.error('MongoDB error:', error);
res.status(500).json({ error: 'Internal error' });
}
});
app.listen(3000, () => {
console.log('MongoDB sync webhook running on port 3000');
});
Deployment Options
Serverless Deployment
- Vercel
- AWS Lambda
// api/webhooks/nudj.js
export default async function handler(req, res) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
if (req.headers['x-api-key'] !== process.env.WEBHOOK_SECRET) {
return res.status(401).json({ error: 'Unauthorized' });
}
const event = req.body;
// Process webhook
await processWebhook(event);
res.status(200).json({ success: true });
}
// handler.js
exports.handler = async (event) => {
const body = JSON.parse(event.body);
const apiKey = event.headers['x-api-key'];
if (apiKey !== process.env.WEBHOOK_SECRET) {
return {
statusCode: 401,
body: JSON.stringify({ error: 'Unauthorized' })
};
}
// Process webhook
await processWebhook(body);
return {
statusCode: 200,
body: JSON.stringify({ success: true })
};
};

