diff --git a/jellyseerr-api.yml b/jellyseerr-api.yml
index 752b85f8..2535059a 100644
--- a/jellyseerr-api.yml
+++ b/jellyseerr-api.yml
@@ -1451,6 +1451,9 @@ components:
type: string
jsonPayload:
type: string
+ supportVariables:
+ type: boolean
+ example: false
TelegramSettings:
type: object
properties:
diff --git a/server/lib/notifications/agents/webhook.ts b/server/lib/notifications/agents/webhook.ts
index c441cb65..d8ddcb23 100644
--- a/server/lib/notifications/agents/webhook.ts
+++ b/server/lib/notifications/agents/webhook.ts
@@ -177,9 +177,27 @@ class WebhookAgent
subject: payload.subject,
});
+ let webhookUrl = settings.options.webhookUrl;
+
+ if (settings.options.supportVariables) {
+ Object.keys(KeyMap).forEach((keymapKey) => {
+ const keymapValue = KeyMap[keymapKey as keyof typeof KeyMap];
+ const variableValue =
+ type === Notification.TEST_NOTIFICATION
+ ? 'test'
+ : typeof keymapValue === 'function'
+ ? keymapValue(payload, type)
+ : get(payload, keymapValue) || 'test';
+ webhookUrl = webhookUrl.replace(
+ new RegExp(`{{${keymapKey}}}`, 'g'),
+ encodeURIComponent(variableValue)
+ );
+ });
+ }
+
try {
await axios.post(
- settings.options.webhookUrl,
+ webhookUrl,
this.buildPayload(type, payload),
settings.options.authHeader
? {
diff --git a/server/lib/settings/index.ts b/server/lib/settings/index.ts
index 29c7ed04..ce3e4b24 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;
+ supportVariables?: boolean;
};
}
diff --git a/server/routes/settings/notifications.ts b/server/routes/settings/notifications.ts
index 122ef017..5984e538 100644
--- a/server/routes/settings/notifications.ts
+++ b/server/routes/settings/notifications.ts
@@ -279,6 +279,7 @@ notificationRoutes.get('/webhook', (_req, res) => {
'utf8'
)
),
+ supportVariables: webhookSettings.options.supportVariables ?? false,
},
};
@@ -300,6 +301,7 @@ notificationRoutes.post('/webhook', async (req, res, next) => {
),
webhookUrl: req.body.options.webhookUrl,
authHeader: req.body.options.authHeader,
+ supportVariables: req.body.options.supportVariables ?? false,
},
};
await settings.save();
@@ -331,6 +333,7 @@ notificationRoutes.post('/webhook/test', async (req, res, next) => {
),
webhookUrl: req.body.options.webhookUrl,
authHeader: req.body.options.authHeader,
+ 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 0595090f..2c3d436d 100644
--- a/src/components/Settings/Notifications/NotificationsWebhook/index.tsx
+++ b/src/components/Settings/Notifications/NotificationsWebhook/index.tsx
@@ -1,6 +1,7 @@
import Button from '@app/components/Common/Button';
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
+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';
@@ -73,6 +74,11 @@ const messages = defineMessages(
{
agentenabled: 'Enable Agent',
webhookUrl: 'Webhook URL',
+ webhookUrlTip:
+ 'Test Notification URL is set to {testUrl} instead of the actual webhook URL.',
+ supportVariables: 'Support URL Variables',
+ supportVariablesTip:
+ 'Available variables are documented in the webhook template variables section',
authheader: 'Authorization Header',
validationJsonPayloadRequired: 'You must provide a valid JSON payload',
webhooksettingssaved: 'Webhook notification settings saved successfully!',
@@ -111,8 +117,14 @@ const NotificationsWebhook = () => {
.test(
'valid-url',
intl.formatMessage(messages.validationWebhookUrl),
- isValidURL
+ function (value) {
+ const { supportVariables } = this.parent;
+ return supportVariables || isValidURL(value);
+ }
),
+
+ supportVariables: Yup.boolean(),
+
jsonPayload: Yup.string()
.when('enabled', {
is: true,
@@ -147,6 +159,7 @@ const NotificationsWebhook = () => {
webhookUrl: data.options.webhookUrl,
jsonPayload: data.options.jsonPayload,
authHeader: data.options.authHeader,
+ supportVariables: data.options.supportVariables ?? false,
}}
validationSchema={NotificationsWebhookSchema}
onSubmit={async (values) => {
@@ -158,6 +171,7 @@ const NotificationsWebhook = () => {
webhookUrl: values.webhookUrl,
jsonPayload: JSON.stringify(values.jsonPayload),
authHeader: values.authHeader,
+ supportVariables: values.supportVariables,
},
});
addToast(intl.formatMessage(messages.webhooksettingssaved), {
@@ -215,6 +229,7 @@ const NotificationsWebhook = () => {
webhookUrl: values.webhookUrl,
jsonPayload: JSON.stringify(values.jsonPayload),
authHeader: values.authHeader,
+ supportVariables: values.supportVariables ?? false,
},
});
@@ -249,10 +264,59 @@ const NotificationsWebhook = () => {