#!/usr/bin/env node /** * Simple webhook test server for TaskNotes webhook testing * * Usage: node test-webhook.js [port] * Default port: 3000 * * This server will log all incoming webhook payloads and verify signatures. */ const express = require('express'); const crypto = require('crypto'); const app = express(); const PORT = process.argv[2] || 3000; // Middleware app.use(express.json()); // CORS middleware app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-TaskNotes-Event, X-TaskNotes-Signature, X-TaskNotes-Delivery-ID'); // Handle preflight requests if (req.method === 'OPTIONS') { res.sendStatus(200); return; } next(); }); // Test webhook secret (you should use this when configuring the webhook in TaskNotes) const WEBHOOK_SECRET = 'test-secret-key-for-tasknotes-webhooks'; console.log('='.repeat(60)); console.log('TaskNotes Webhook Test Server'); console.log('='.repeat(60)); console.log(`Server running on: http://localhost:${PORT}`); console.log(`Webhook URL: http://localhost:${PORT}/webhook`); console.log(`Test secret: ${WEBHOOK_SECRET}`); console.log('='.repeat(60)); /** * Verify webhook signature */ function verifySignature(payload, signature, secret) { if (!signature) return false; const expectedSignature = crypto .createHmac('sha256', secret) .update(JSON.stringify(payload)) .digest('hex'); return signature === expectedSignature; } /** * Main webhook endpoint */ app.post('/webhook', (req, res) => { const timestamp = new Date().toISOString(); const signature = req.headers['x-tasknotes-signature']; const event = req.headers['x-tasknotes-event']; const deliveryId = req.headers['x-tasknotes-delivery-id']; console.log('\n' + '='.repeat(50)); console.log(`šØ Webhook Received [${timestamp}]`); console.log('='.repeat(50)); // Headers console.log('š Headers:'); console.log(` Event Type: ${event || 'unknown'}`); console.log(` Delivery ID: ${deliveryId || 'unknown'}`); console.log(` Signature: ${signature ? signature.substring(0, 16) + '...' : 'missing'}`); // Signature verification const isValidSignature = verifySignature(req.body, signature, WEBHOOK_SECRET); console.log(` Signature Valid: ${isValidSignature ? 'ā ' : 'ā'}`); if (!isValidSignature) { console.log('ā ļø WARNING: Invalid signature! Check your webhook secret.'); } // Payload console.log('\nš¦ Payload:'); console.log(JSON.stringify(req.body, null, 2)); // Event-specific processing if (req.body.event && req.body.data) { console.log('\nš Processing Event:'); switch (req.body.event) { case 'task.created': console.log(` ā New task created: "${req.body.data.task?.title}"`); if (req.body.data.source === 'nlp') { console.log(` š¬ Created via NLP from: "${req.body.data.originalText}"`); } break; case 'task.updated': console.log(` āļø Task updated: "${req.body.data.task?.title}"`); if (req.body.data.previous) { console.log(` š Changed status: ${req.body.data.previous.status} ā ${req.body.data.task?.status}`); } break; case 'task.completed': console.log(` ā Task completed: "${req.body.data.task?.title}"`); break; case 'task.deleted': console.log(` šļø Task deleted: "${req.body.data.task?.title}"`); break; case 'task.archived': console.log(` š¦ Task archived: "${req.body.data.task?.title}"`); break; case 'time.started': console.log(` ā° Time tracking started: "${req.body.data.task?.title}"`); break; case 'time.stopped': console.log(` ā¹ļø Time tracking stopped: "${req.body.data.task?.title}"`); break; case 'reminder.triggered': console.log(` š Reminder triggered: "${req.body.data.task?.title}"`); console.log(` š Message: "${req.body.data.message}"`); console.log(` š Notification time: ${req.body.data.notificationTime}`); break; default: console.log(` š Unknown event: ${req.body.event}`); } } console.log('='.repeat(50)); // Always respond with 200 OK res.status(200).json({ received: true, timestamp, event, deliveryId, signatureValid: isValidSignature }); }); /** * Health check endpoint */ app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString(), message: 'TaskNotes webhook test server is running' }); }); /** * Root endpoint with instructions */ app.get('/', (req, res) => { res.send(`
This server is ready to receive TaskNotes webhooks!
http://localhost:${PORT}/webhook${WEBHOOK_SECRET}http://localhost:${PORT}/webhook${WEBHOOK_SECRET}Check the console output to see webhook payloads as they arrive.
`); }); // Error handling app.use((error, req, res, next) => { console.error('ā Server Error:', error); res.status(500).json({ error: 'Internal server error' }); }); // Start server app.listen(PORT, () => { console.log(`\nš Ready to receive webhooks!`); console.log(`\nTo test:`); console.log(`1. Add webhook URL: http://localhost:${PORT}/webhook`); console.log(`2. Use secret: ${WEBHOOK_SECRET}`); console.log(`3. Perform actions in TaskNotes`); console.log(`4. Watch the console for webhook events!\n`); }); // Handle graceful shutdown process.on('SIGINT', () => { console.log('\nš Shutting down webhook test server...'); process.exit(0); });