#!/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(`

TaskNotes Webhook Test Server

This server is ready to receive TaskNotes webhooks!

Configuration:

Setup Instructions:

  1. Open TaskNotes Settings → HTTP API tab
  2. Enable HTTP API if not already enabled
  3. Click "Add Webhook" in the Webhook Settings section
  4. Enter URL: http://localhost:${PORT}/webhook
  5. Select the events you want to test
  6. The secret will be auto-generated - replace it with: ${WEBHOOK_SECRET}
  7. Save the webhook configuration
  8. Perform actions in TaskNotes to trigger webhook events!

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); });