// Copyright (c) 2026 Gordon Bolton. MIT License. const express = require('express'); const { logToFile } = require('../utils/logger'); const { getWebhookSecret } = require('../utils/config'); const router = express.Router(); /** * Validate webhook secret from the X-Sofarr-Webhook-Secret header * @param {Object} req - Express request object * @returns {boolean} True if secret is valid, false otherwise */ function validateWebhookSecret(req) { const expectedSecret = getWebhookSecret(); const providedSecret = req.get('X-Sofarr-Webhook-Secret'); if (!expectedSecret) { logToFile('[Webhook] WARNING: SOFARR_WEBHOOK_SECRET not configured, rejecting webhook'); return false; } if (!providedSecret) { logToFile('[Webhook] WARNING: Missing X-Sofarr-Webhook-Secret header'); return false; } if (providedSecret !== expectedSecret) { logToFile('[Webhook] WARNING: Invalid webhook secret provided'); return false; } return true; } /** * POST /api/webhook/sonarr * Receives webhook events from Sonarr instances. * Validates the secret, logs the event, and returns 200 immediately. * * Phase 1: Receiver only - no cache or SSE integration yet. * Future phases will integrate with PALDRA registry for event-driven updates. */ router.post('/sonarr', (req, res) => { if (!validateWebhookSecret(req)) { return res.status(401).json({ error: 'Unauthorized' }); } try { const { eventType, instanceName } = req.body || {}; logToFile(`[Webhook] Sonarr event received - Type: ${eventType || 'unknown'}, Instance: ${instanceName || 'unknown'}`); logToFile(`[Webhook] Sonarr payload: ${JSON.stringify(req.body)}`); // Phase 1: Log and respond immediately // Future phases will push to cache and trigger SSE res.status(200).json({ received: true }); } catch (error) { logToFile(`[Webhook] Sonarr error: ${error.message}`); res.status(200).json({ received: true }); // Still return 200 to avoid webhook retries } }); /** * POST /api/webhook/radarr * Receives webhook events from Radarr instances. * Validates the secret, logs the event, and returns 200 immediately. * * Phase 1: Receiver only - no cache or SSE integration yet. * Future phases will integrate with PALDRA registry for event-driven updates. */ router.post('/radarr', (req, res) => { if (!validateWebhookSecret(req)) { return res.status(401).json({ error: 'Unauthorized' }); } try { const { eventType, instanceName } = req.body || {}; logToFile(`[Webhook] Radarr event received - Type: ${eventType || 'unknown'}, Instance: ${instanceName || 'unknown'}`); logToFile(`[Webhook] Radarr payload: ${JSON.stringify(req.body)}`); // Phase 1: Log and respond immediately // Future phases will push to cache and trigger SSE res.status(200).json({ received: true }); } catch (error) { logToFile(`[Webhook] Radarr error: ${error.message}`); res.status(200).json({ received: true }); // Still return 200 to avoid webhook retries } }); module.exports = router;