diff --git a/server/lib/notifications/agents/webhook.ts b/server/lib/notifications/agents/webhook.ts index d8ddcb23..929415c9 100644 --- a/server/lib/notifications/agents/webhook.ts +++ b/server/lib/notifications/agents/webhook.ts @@ -196,16 +196,33 @@ class WebhookAgent } try { + const headers: Record = {}; + + if (settings.options.authHeader) { + headers.Authorization = settings.options.authHeader; + } + + if ( + settings.options.customHeaders && + settings.options.customHeaders.length > 0 + ) { + settings.options.customHeaders.forEach((header) => { + if (header.key && header.value) { + // Don't override Authorization header if it's already set via authHeader + if ( + header.key.toLowerCase() !== 'authorization' || + !settings.options.authHeader + ) { + headers[header.key] = header.value; + } + } + }); + } + await axios.post( webhookUrl, this.buildPayload(type, payload), - settings.options.authHeader - ? { - headers: { - Authorization: settings.options.authHeader, - }, - } - : undefined + Object.keys(headers).length > 0 ? { headers } : undefined ); return true; diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts index e37eccc9..7bca1d0e 100644 --- a/server/lib/settings/index.ts +++ b/server/lib/settings/index.ts @@ -275,6 +275,7 @@ export interface NotificationAgentWebhook extends NotificationAgentConfig { webhookUrl: string; jsonPayload: string; authHeader?: string; + customHeaders?: { key: string; value: string }[]; supportVariables?: boolean; }; } diff --git a/server/routes/settings/notifications.ts b/server/routes/settings/notifications.ts index 5984e538..52cc2ee0 100644 --- a/server/routes/settings/notifications.ts +++ b/server/routes/settings/notifications.ts @@ -279,6 +279,7 @@ notificationRoutes.get('/webhook', (_req, res) => { 'utf8' ) ), + customHeaders: webhookSettings.options.customHeaders ?? [], supportVariables: webhookSettings.options.supportVariables ?? false, }, }; @@ -301,6 +302,7 @@ notificationRoutes.post('/webhook', async (req, res, next) => { ), webhookUrl: req.body.options.webhookUrl, authHeader: req.body.options.authHeader, + customHeaders: req.body.options.customHeaders ?? [], supportVariables: req.body.options.supportVariables ?? false, }, }; @@ -333,6 +335,7 @@ notificationRoutes.post('/webhook/test', async (req, res, next) => { ), webhookUrl: req.body.options.webhookUrl, authHeader: req.body.options.authHeader, + customHeaders: req.body.options.customHeaders ?? [], supportVariables: req.body.options.supportVariables ?? false, }, }; diff --git a/src/components/Settings/Notifications/NotificationsWebhook/index.tsx b/src/components/Settings/Notifications/NotificationsWebhook/index.tsx index 3b13b105..775f5b2b 100644 --- a/src/components/Settings/Notifications/NotificationsWebhook/index.tsx +++ b/src/components/Settings/Notifications/NotificationsWebhook/index.tsx @@ -5,7 +5,12 @@ import SettingsBadge from '@app/components/Settings/SettingsBadge'; import globalMessages from '@app/i18n/globalMessages'; import defineMessages from '@app/utils/defineMessages'; import { isValidURL } from '@app/utils/urlValidationHelper'; -import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline'; +import { + ArrowDownOnSquareIcon, + BeakerIcon, + PlusIcon, + TrashIcon, +} from '@heroicons/react/24/outline'; import { ArrowPathIcon, QuestionMarkCircleIcon, @@ -80,6 +85,15 @@ const messages = defineMessages( supportVariablesTip: 'Available variables are documented in the webhook template variables section', authheader: 'Authorization Header', + customHeaders: 'Custom Headers', + customHeadersTip: + 'Add custom HTTP headers to include with webhook requests', + customHeadersAdd: 'Add Header', + customHeadersRemove: 'Remove', + customHeadersKey: 'Header Name', + customHeadersValue: 'Header Value', + customHeadersKeyRequired: 'Header name is required', + customHeadersValueRequired: 'Header value is required', validationJsonPayloadRequired: 'You must provide a valid JSON payload', webhooksettingssaved: 'Webhook notification settings saved successfully!', webhooksettingsfailed: 'Webhook notification settings failed to save.', @@ -125,6 +139,8 @@ const NotificationsWebhook = () => { supportVariables: Yup.boolean(), + customHeaders: Yup.array(), + jsonPayload: Yup.string() .when('enabled', { is: true, @@ -159,6 +175,7 @@ const NotificationsWebhook = () => { webhookUrl: data.options.webhookUrl, jsonPayload: data.options.jsonPayload, authHeader: data.options.authHeader, + customHeaders: data.options.customHeaders ?? [], supportVariables: data.options.supportVariables ?? false, }} validationSchema={NotificationsWebhookSchema} @@ -171,6 +188,9 @@ const NotificationsWebhook = () => { webhookUrl: values.webhookUrl, jsonPayload: JSON.stringify(values.jsonPayload), authHeader: values.authHeader, + customHeaders: values.customHeaders.filter( + (h: { key: string; value: string }) => h.key && h.value + ), supportVariables: values.supportVariables, }, }); @@ -229,6 +249,9 @@ const NotificationsWebhook = () => { webhookUrl: values.webhookUrl, jsonPayload: JSON.stringify(values.jsonPayload), authHeader: values.authHeader, + customHeaders: values.customHeaders.filter( + (h: { key: string; value: string }) => h.key && h.value + ), supportVariables: values.supportVariables ?? false, }, }); @@ -344,6 +367,81 @@ const NotificationsWebhook = () => { +
+ +
+
+ {values.customHeaders.map( + (header: { key: string; value: string }, index: number) => ( +
+
+
+ +
+
+
+
+ +
+
+
+ +
+
+ ) + )} + +
+
+