Skip to content

Commit 17172e9

Browse files
feat(webhook): add support for dynamic placeholders in webhook URL (#1491)
* feat(wehbook): add support for dynamic placeholders in webhook URL * refactor(webhook): rename supportPlaceholders to supportVariables and update related logic Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me> * feat(i18n): add missing translations Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me> * refactor(notifications): simplify webhook URL validation logic Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me> * fix: wrong docs url Co-authored-by: Gauthier <mail@gauthierth.fr> * fix: update webhook documentation URL to point to Jellyseerr Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me> --------- Signed-off-by: 0xsysr3ll <0xsysr3ll@pm.me> Co-authored-by: Gauthier <mail@gauthierth.fr>
1 parent 4878722 commit 17172e9

6 files changed

Lines changed: 95 additions & 3 deletions

File tree

jellyseerr-api.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1451,6 +1451,9 @@ components:
14511451
type: string
14521452
jsonPayload:
14531453
type: string
1454+
supportVariables:
1455+
type: boolean
1456+
example: false
14541457
TelegramSettings:
14551458
type: object
14561459
properties:

server/lib/notifications/agents/webhook.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,27 @@ class WebhookAgent
177177
subject: payload.subject,
178178
});
179179

180+
let webhookUrl = settings.options.webhookUrl;
181+
182+
if (settings.options.supportVariables) {
183+
Object.keys(KeyMap).forEach((keymapKey) => {
184+
const keymapValue = KeyMap[keymapKey as keyof typeof KeyMap];
185+
const variableValue =
186+
type === Notification.TEST_NOTIFICATION
187+
? 'test'
188+
: typeof keymapValue === 'function'
189+
? keymapValue(payload, type)
190+
: get(payload, keymapValue) || 'test';
191+
webhookUrl = webhookUrl.replace(
192+
new RegExp(`{{${keymapKey}}}`, 'g'),
193+
encodeURIComponent(variableValue)
194+
);
195+
});
196+
}
197+
180198
try {
181199
await axios.post(
182-
settings.options.webhookUrl,
200+
webhookUrl,
183201
this.buildPayload(type, payload),
184202
settings.options.authHeader
185203
? {

server/lib/settings/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ export interface NotificationAgentWebhook extends NotificationAgentConfig {
275275
webhookUrl: string;
276276
jsonPayload: string;
277277
authHeader?: string;
278+
supportVariables?: boolean;
278279
};
279280
}
280281

server/routes/settings/notifications.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,7 @@ notificationRoutes.get('/webhook', (_req, res) => {
279279
'utf8'
280280
)
281281
),
282+
supportVariables: webhookSettings.options.supportVariables ?? false,
282283
},
283284
};
284285

@@ -300,6 +301,7 @@ notificationRoutes.post('/webhook', async (req, res, next) => {
300301
),
301302
webhookUrl: req.body.options.webhookUrl,
302303
authHeader: req.body.options.authHeader,
304+
supportVariables: req.body.options.supportVariables ?? false,
303305
},
304306
};
305307
await settings.save();
@@ -331,6 +333,7 @@ notificationRoutes.post('/webhook/test', async (req, res, next) => {
331333
),
332334
webhookUrl: req.body.options.webhookUrl,
333335
authHeader: req.body.options.authHeader,
336+
supportVariables: req.body.options.supportVariables ?? false,
334337
},
335338
};
336339

src/components/Settings/Notifications/NotificationsWebhook/index.tsx

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Button from '@app/components/Common/Button';
22
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
33
import NotificationTypeSelector from '@app/components/NotificationTypeSelector';
4+
import SettingsBadge from '@app/components/Settings/SettingsBadge';
45
import globalMessages from '@app/i18n/globalMessages';
56
import defineMessages from '@app/utils/defineMessages';
67
import { isValidURL } from '@app/utils/urlValidationHelper';
@@ -73,6 +74,11 @@ const messages = defineMessages(
7374
{
7475
agentenabled: 'Enable Agent',
7576
webhookUrl: 'Webhook URL',
77+
webhookUrlTip:
78+
'Test Notification URL is set to {testUrl} instead of the actual webhook URL.',
79+
supportVariables: 'Support URL Variables',
80+
supportVariablesTip:
81+
'Available variables are documented in the webhook template variables section',
7682
authheader: 'Authorization Header',
7783
validationJsonPayloadRequired: 'You must provide a valid JSON payload',
7884
webhooksettingssaved: 'Webhook notification settings saved successfully!',
@@ -111,8 +117,14 @@ const NotificationsWebhook = () => {
111117
.test(
112118
'valid-url',
113119
intl.formatMessage(messages.validationWebhookUrl),
114-
isValidURL
120+
function (value) {
121+
const { supportVariables } = this.parent;
122+
return supportVariables || isValidURL(value);
123+
}
115124
),
125+
126+
supportVariables: Yup.boolean(),
127+
116128
jsonPayload: Yup.string()
117129
.when('enabled', {
118130
is: true,
@@ -147,6 +159,7 @@ const NotificationsWebhook = () => {
147159
webhookUrl: data.options.webhookUrl,
148160
jsonPayload: data.options.jsonPayload,
149161
authHeader: data.options.authHeader,
162+
supportVariables: data.options.supportVariables ?? false,
150163
}}
151164
validationSchema={NotificationsWebhookSchema}
152165
onSubmit={async (values) => {
@@ -158,6 +171,7 @@ const NotificationsWebhook = () => {
158171
webhookUrl: values.webhookUrl,
159172
jsonPayload: JSON.stringify(values.jsonPayload),
160173
authHeader: values.authHeader,
174+
supportVariables: values.supportVariables,
161175
},
162176
});
163177
addToast(intl.formatMessage(messages.webhooksettingssaved), {
@@ -215,6 +229,7 @@ const NotificationsWebhook = () => {
215229
webhookUrl: values.webhookUrl,
216230
jsonPayload: JSON.stringify(values.jsonPayload),
217231
authHeader: values.authHeader,
232+
supportVariables: values.supportVariables ?? false,
218233
},
219234
});
220235

@@ -249,10 +264,59 @@ const NotificationsWebhook = () => {
249264
<Field type="checkbox" id="enabled" name="enabled" />
250265
</div>
251266
</div>
267+
<div className="form-row">
268+
<label htmlFor="supportVariables" className="checkbox-label">
269+
<span className="mr-2">
270+
{intl.formatMessage(messages.supportVariables)}
271+
</span>
272+
<SettingsBadge badgeType="experimental" />
273+
<span className="label-tip">
274+
{intl.formatMessage(messages.supportVariablesTip)}
275+
</span>
276+
</label>
277+
<div className="form-input-area">
278+
<Field
279+
type="checkbox"
280+
id="supportVariables"
281+
name="supportVariables"
282+
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
283+
setFieldValue('supportVariables', e.target.checked)
284+
}
285+
/>
286+
</div>
287+
</div>
288+
{values.supportVariables && (
289+
<div className="mt-2">
290+
<Link
291+
href="https://docs.jellyseerr.dev/using-jellyseerr/notifications/webhook#template-variables"
292+
passHref
293+
legacyBehavior
294+
>
295+
<Button
296+
as="a"
297+
buttonSize="sm"
298+
target="_blank"
299+
rel="noreferrer"
300+
>
301+
<QuestionMarkCircleIcon />
302+
<span>
303+
{intl.formatMessage(messages.templatevariablehelp)}
304+
</span>
305+
</Button>
306+
</Link>
307+
</div>
308+
)}
252309
<div className="form-row">
253310
<label htmlFor="webhookUrl" className="text-label">
254311
{intl.formatMessage(messages.webhookUrl)}
255312
<span className="label-required">*</span>
313+
{values.supportVariables && (
314+
<div className="label-tip">
315+
{intl.formatMessage(messages.webhookUrlTip, {
316+
testUrl: '/test',
317+
})}
318+
</div>
319+
)}
256320
</label>
257321
<div className="form-input-area">
258322
<div className="form-input-field">
@@ -312,7 +376,7 @@ const NotificationsWebhook = () => {
312376
<span>{intl.formatMessage(messages.resetPayload)}</span>
313377
</Button>
314378
<Link
315-
href="https://docs.overseerr.dev/using-overseerr/notifications/webhooks#template-variables"
379+
href="https://docs.jellyseerr.dev/using-jellyseerr/notifications/webhook#template-variables"
316380
passHref
317381
legacyBehavior
318382
>

src/i18n/locale/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,8 @@
683683
"components.Settings.Notifications.NotificationsWebhook.customJson": "JSON Payload",
684684
"components.Settings.Notifications.NotificationsWebhook.resetPayload": "Reset to Default",
685685
"components.Settings.Notifications.NotificationsWebhook.resetPayloadSuccess": "JSON payload reset successfully!",
686+
"components.Settings.Notifications.NotificationsWebhook.supportVariables": "Support URL Variables",
687+
"components.Settings.Notifications.NotificationsWebhook.supportVariablesTip": "Available variables are documented in the webhook template variables section",
686688
"components.Settings.Notifications.NotificationsWebhook.templatevariablehelp": "Template Variable Help",
687689
"components.Settings.Notifications.NotificationsWebhook.toastWebhookTestFailed": "Webhook test notification failed to send.",
688690
"components.Settings.Notifications.NotificationsWebhook.toastWebhookTestSending": "Sending webhook test notification…",
@@ -691,6 +693,7 @@
691693
"components.Settings.Notifications.NotificationsWebhook.validationTypes": "You must select at least one notification type",
692694
"components.Settings.Notifications.NotificationsWebhook.validationWebhookUrl": "You must provide a valid URL",
693695
"components.Settings.Notifications.NotificationsWebhook.webhookUrl": "Webhook URL",
696+
"components.Settings.Notifications.NotificationsWebhook.webhookUrlTip": "Test Notification URL is set to {testUrl} instead of the actual webhook URL.",
694697
"components.Settings.Notifications.NotificationsWebhook.webhooksettingsfailed": "Webhook notification settings failed to save.",
695698
"components.Settings.Notifications.NotificationsWebhook.webhooksettingssaved": "Webhook notification settings saved successfully!",
696699
"components.Settings.Notifications.NotificationsWebPush.agentenabled": "Enable Agent",

0 commit comments

Comments
 (0)