feat: generic webhook provider with JSONPath payload extraction

Add a new "webhook" provider type that accepts arbitrary HTTP POST payloads,
extracts template variables via user-defined JSONPath mappings, and dispatches
notifications through the existing pipeline. Supports three auth modes
(HMAC-SHA256, Bearer token, none), bounded JSONPath cache, and 1MB payload limit.

Full stack: core provider + event parser, API endpoint, DB migration,
capabilities, seeds, default templates (EN/RU), frontend descriptor, i18n.
This commit is contained in:
2026-03-27 23:51:14 +03:00
parent 307871cae5
commit 616b221c92
38 changed files with 603 additions and 0 deletions
+2
View File
@@ -12,6 +12,7 @@ import { plankaDescriptor } from './planka';
import { schedulerDescriptor } from './scheduler';
import { nutDescriptor } from './nut';
import { googlePhotosDescriptor } from './google-photos';
import { webhookDescriptor } from './webhook';
const REGISTRY: ReadonlyMap<string, ProviderDescriptor> = new Map([
['immich', immichDescriptor],
@@ -20,6 +21,7 @@ const REGISTRY: ReadonlyMap<string, ProviderDescriptor> = new Map([
['scheduler', schedulerDescriptor],
['nut', nutDescriptor],
['google_photos', googlePhotosDescriptor],
['webhook', webhookDescriptor],
]);
/** Look up a provider descriptor by type. Returns null for unknown types. */
+49
View File
@@ -0,0 +1,49 @@
import type { ProviderDescriptor } from './types';
export const webhookDescriptor: ProviderDescriptor = {
type: 'webhook',
defaultName: 'Generic Webhook',
icon: 'mdiWebhook',
hasUrl: false,
configFields: [
{
key: 'auth_mode', configKey: 'auth_mode',
label: 'providers.authMode',
type: 'text',
placeholder: 'hmac_sha256 | bearer_token | none',
defaultValue: 'none',
hint: 'providers.authModeHint',
},
{
key: 'webhook_secret', configKey: 'webhook_secret',
label: 'providers.webhookSecret', editLabel: 'providers.webhookSecretKeep',
type: 'password', optional: true,
hint: 'providers.genericWebhookSecretHint',
},
],
buildConfig(form, editing) {
const config: Record<string, any> = {
auth_mode: form.auth_mode || 'none',
payload_mappings: form.payload_mappings || [],
};
if (form.webhook_secret) config.webhook_secret = form.webhook_secret;
if (form.event_type_path) config.event_type_path = form.event_type_path;
if (form.collection_path) config.collection_path = form.collection_path;
return { config };
},
hasConfigChanged(form, existing) {
return form.auth_mode !== (existing.auth_mode || 'none') ||
!!form.webhook_secret;
},
eventFields: [
{ key: 'track_webhook_received', label: 'trackingConfig.webhookReceived', default: true },
],
collectionMeta: null,
webhookBased: true,
webhookUrlPattern: '/api/webhooks/webhook/{id}',
};