diff --git a/docs/WEBHOOKS.md b/docs/WEBHOOKS.md index 6dcc6a1fd..9899d5706 100644 --- a/docs/WEBHOOKS.md +++ b/docs/WEBHOOKS.md @@ -28,8 +28,8 @@ Pulse includes built-in templates for popular services and a generic JSON templa For generic webhooks, use Go templates to format the JSON payload. **Variables (common):** -- `{{.ID}}`, `{{.Level}}`, `{{.Type}}` -- `{{.ResourceName}}`, `{{.ResourceID}}`, `{{.ResourceType}}`, `{{.Node}}` +- `{{.ID}}`, `{{.Event}}`, `{{.MessageKey}}`, `{{.Level}}`, `{{.Type}}` +- `{{.ResourceName}}`, `{{.ResourceID}}`, `{{.ResourceType}}`, `{{.Node}}`, `{{.NodeDisplayName}}` - `{{.Message}}`, `{{.Value}}`, `{{.Threshold}}`, `{{.Duration}}`, `{{.Timestamp}}` - `{{.Instance}}` (Pulse public URL if configured) - `{{.TenantID}}`, `{{.TenantName}}` (tenant identity in multi-tenant orgs and MSP client runtimes; empty on plain single-tenant installs) @@ -71,6 +71,8 @@ These fields and behaviors are stable; ticket-routing integrations can rely on t **Alert type.** `{{.Type}}` is the metric or condition that fired: `cpu`, `memory`, `disk`, `diskRead`, `diskWrite`, `networkIn`, `networkOut`, `connectivity`, and similar. The alert ID (`{{.ID}}`) is stable for the lifetime of an alert occurrence, so the `resolved` event carries the same ID as the `alert` event it closes. +**Message key.** `{{.MessageKey}}` is the stable, language-neutral condition key for rebuilding or translating a notification. Canonical alerts use `.` (for example, `metric-threshold.disk`); older alert paths fall back to `{{.Type}}`. Combine it with `{{.Event}}` and `{{.ResourceType}}` when the receiving system needs separate wording for firing/recovery events or different resource classes. Unlike a numeric message index, the symbolic key does not change when another alert type is added. + **Tenant identity.** In multi-tenant organizations and MSP client runtimes, `{{.TenantID}}` and `{{.TenantName}}` identify which tenant fired the alert. Client runtimes get identity from the `PULSE_TENANT_ID` / `PULSE_TENANT_NAME` environment; shared-process organizations stamp the org ID and display name automatically. **Resource tag routing.** Email and each alert webhook can be limited to resources with selected tags in **Alerts → Notifications**. An empty filter receives every alert. With multiple tags, choose **Match all tags** or **Match any tag**. Matching ignores case. Proxmox tags are matched as shown; Docker container and service labels are exposed as `key:value` tags (or `key` when the label value is empty). Recovery notifications follow the destinations that received the firing alert, even if a resource's tags change before recovery. @@ -192,11 +194,13 @@ stable severity/type fields from the [delivery contract](#-delivery-contract): { "event": "{{.Event}}", "alertId": "{{.ID | jsonString}}", + "messageKey": "{{.MessageKey | jsonString}}", "severity": "{{.Level | jsonString}}", "alertType": "{{.Type | jsonString}}", "tenantId": "{{.TenantID | jsonString}}", "tenantName": "{{.TenantName | jsonString}}", "resource": "{{.ResourceName | jsonString}}", + "resourceType": "{{.ResourceType | jsonString}}", "node": "{{.Node | jsonString}}", "summary": "{{.Message | jsonString}}", "value": {{.Value}}, diff --git a/docs/release-control/v6/internal/subsystems/alerts.md b/docs/release-control/v6/internal/subsystems/alerts.md index 79f37ecde..93ce62d1b 100644 --- a/docs/release-control/v6/internal/subsystems/alerts.md +++ b/docs/release-control/v6/internal/subsystems/alerts.md @@ -434,6 +434,12 @@ inspectability, or convert missing/stale evidence into health. ## Current State +The alert webhook editor exposes the delivery contract's language-neutral +`MessageKey`, event, resource type and node display name alongside the existing +type, severity and metric fields. Presentation must advertise the backend-owned +template data rather than asking operators to parse the English alert message +or reach into metadata for canonical alert identity. + ### Agent custom sensors use canonical health-assessment alerts Typed `HostSensorSummary.Custom` numeric, boolean, or timestamp readings with diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index ecb8e14ad..110160bfd 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -25,6 +25,9 @@ in the existing customer-facing section and have exact packet proof in `render_release_body_test.py`; a note about repository-scoped Proxmox backup review must name both PBS server and datastore rather than implying that a workload or node filter provides the same boundary. +The next-candidate release notes and changelog must also describe newly stable +integration fields when external receivers need them to consume the release; +packet proof keeps the customer-facing summary and detailed changelog aligned. Provider-hosted MSP deploy artifacts must package the provider control plane as a least-privilege Docker provisioner. The packaged compose/setup path must avoid diff --git a/docs/release-control/v6/internal/subsystems/notifications.md b/docs/release-control/v6/internal/subsystems/notifications.md index 6446eb8e4..72ddbe742 100644 --- a/docs/release-control/v6/internal/subsystems/notifications.md +++ b/docs/release-control/v6/internal/subsystems/notifications.md @@ -198,6 +198,13 @@ its tenant block only when an identity is present so single-tenant payloads keep their existing shape. PSA/ticket-bridge receivers must get tenant routing identity from this payload boundary, not by inferring it from webhook endpoint configuration. +That payload boundary also owns the language-neutral `MessageKey` exposed to +custom templates. Canonical alerts derive it from `canonicalAlertKind` and the +alert type (for example `metric-threshold.disk`); legacy alert paths use the +alert type directly. The generic JSON templates must emit the message key, +event, resource type, node display name and formatted metric values explicitly +so external receivers can translate or reconstruct notifications without +parsing English message text or reaching into the metadata map. That same transport boundary also owns outbound delivery integrity. A webhook config may carry an optional signing secret; when present, every JSON delivery through the canonical webhook transport must send `X-Pulse-Timestamp` and diff --git a/docs/releases/RELEASE_NOTES_v6.4.0-rc.3.md b/docs/releases/RELEASE_NOTES_v6.4.0-rc.3.md index 571cb6d20..a66a24f7c 100644 --- a/docs/releases/RELEASE_NOTES_v6.4.0-rc.3.md +++ b/docs/releases/RELEASE_NOTES_v6.4.0-rc.3.md @@ -7,6 +7,7 @@ - **Faster large-estate navigation** — Storage and platform cold loads fell from roughly 15.6 seconds to 1.1 seconds in the measured large-estate case, with windowed tables keeping scrolling responsive. - **Lighter live updates** — The `/api/state` response fell from 4.75 MB to 4.09 MB, while changed-item catch-up and faster patch merging reduce work after reconnects and open mobile Alerts reliably. - **Visible notification delivery** — A recent-delivery log shows redacted outcomes, and test sends now say when delivery is paused instead of implying that a message was sent. +- **Translation-ready webhooks** — Generic alert payloads include a stable message key and explicit resource context, so external systems can rebuild notifications in another language without parsing Pulse's English message. - **Safer agent operation** — A least-privilege install profile limits the agent account and sudo access, while Docker and Kubernetes recovery controls allow deliberate re-enrolment after removal. - **Clearer plan choices** — MSP evaluation mode supports provider trials, and the Business tier is now available for teams that need the annual business plan. - **Simpler saved filtering** — Saved views have been removed; bookmarkable filtered URLs remain available for returning to useful table filters. diff --git a/docs/releases/V6_CHANGELOG_v6.4.0-rc.3.md b/docs/releases/V6_CHANGELOG_v6.4.0-rc.3.md index eb4f03e68..9c6109ebb 100644 --- a/docs/releases/V6_CHANGELOG_v6.4.0-rc.3.md +++ b/docs/releases/V6_CHANGELOG_v6.4.0-rc.3.md @@ -7,6 +7,9 @@ next minor release._ ## Added +- Generic alert webhooks expose a stable language-neutral message key plus + explicit event, resource type, display name and formatted metric fields for + external notification translation and reconstruction. - Notification delivery history exposes recent redacted delivery outcomes, while test sends report when delivery is paused instead of claiming success. - A least-privilege Unified Agent install profile creates a non-login service diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index 44f3e4a92..892ad781f 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,19 +1,15 @@ { "version": 1, - "base_sha": "e27585b0a25bfcfc5b99d72f8dd3dcf7c668811e", - "verified_at": "2026-08-26T10:17:12Z", + "base_sha": "4d457159fc7684e7e63c9fd68b85db313300f2ed", + "verified_at": "2026-08-26T11:03:57Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/components/Workloads/guestDrawerModel.ts", - "frontend-modern/src/hooks/useWorkloads.ts", - "frontend-modern/src/types/workloads.ts" + "frontend-modern/src/utils/alertWebhookPresentation.ts" ], "content_sha256": { - "frontend-modern/src/components/Workloads/guestDrawerModel.ts": "8cf63f54a06cd0d2d0a02fbec871c553dc61d31d82724efa4dfa737ddbd5745e", - "frontend-modern/src/hooks/useWorkloads.ts": "87d048e258d71d04087d76d3fb5a3fcf109d454beaed915d1992f50ab4996c94", - "frontend-modern/src/types/workloads.ts": "b10648f1bcc689e1cd078ae1ab8f9f0651a6fab6d0106c30f0eddc797d13c249" + "frontend-modern/src/utils/alertWebhookPresentation.ts": "aaf1bc2327b6b232470569f9110159a88222e7c8cd7b0bf4e473ff0c34eb5147" }, - "routes": ["/proxmox/overview"], + "routes": ["/alerts/notifications"], "viewports": [ { "width": 1280, @@ -25,17 +21,14 @@ } ], "states": [ - "checkout-web-265 VM row filtered and expanded from the Proxmox guest table", - "24-hour History tab with utilization, network I/O, and disk I/O series rendered", - "1-hour History range loaded with the same three drawable chart groups", - "narrow History layout stacked vertically without page-level horizontal overflow", - "drawer collapsed with focus returned to the workload row toggle" + "new Generic webhook editor with its default custom-payload placeholder visible", + "translation-ready variable list showing Event, MessageKey, ResourceType and NodeDisplayName", + "narrow webhook editor with no page-level horizontal overflow" ], "interactions": [ - "signed into the current local build and filtered the Proxmox guest table to checkout-web-265", - "expanded checkout-web-265 and selected History at 1280 by 800; confirmed three chart groups, seven SVG paths, and zero Collecting history placeholders", - "switched the history selector from 24 hours to 1 hour and back; both ranges retained seven drawable SVG paths with no collection placeholder", - "rechecked the open History drawer at 390 by 844; confirmed the three charts stack vertically and the 390-pixel page has no horizontal overflow", - "restored the viewport override, collapsed checkout-web-265, and confirmed focus returned to the row expansion control" + "signed into the current local mock build and opened Alerts, Notifications, then Add Webhook", + "confirmed the Generic payload placeholder includes event, message_key and resource_type at desktop width", + "confirmed the available-variable help exposes MessageKey, ResourceType and NodeDisplayName", + "rechecked the open editor at 390 by 844 and measured document scroll width equal to the 390-pixel viewport" ] } diff --git a/frontend-modern/public/docs/WEBHOOKS.md b/frontend-modern/public/docs/WEBHOOKS.md index 6dcc6a1fd..9899d5706 100644 --- a/frontend-modern/public/docs/WEBHOOKS.md +++ b/frontend-modern/public/docs/WEBHOOKS.md @@ -28,8 +28,8 @@ Pulse includes built-in templates for popular services and a generic JSON templa For generic webhooks, use Go templates to format the JSON payload. **Variables (common):** -- `{{.ID}}`, `{{.Level}}`, `{{.Type}}` -- `{{.ResourceName}}`, `{{.ResourceID}}`, `{{.ResourceType}}`, `{{.Node}}` +- `{{.ID}}`, `{{.Event}}`, `{{.MessageKey}}`, `{{.Level}}`, `{{.Type}}` +- `{{.ResourceName}}`, `{{.ResourceID}}`, `{{.ResourceType}}`, `{{.Node}}`, `{{.NodeDisplayName}}` - `{{.Message}}`, `{{.Value}}`, `{{.Threshold}}`, `{{.Duration}}`, `{{.Timestamp}}` - `{{.Instance}}` (Pulse public URL if configured) - `{{.TenantID}}`, `{{.TenantName}}` (tenant identity in multi-tenant orgs and MSP client runtimes; empty on plain single-tenant installs) @@ -71,6 +71,8 @@ These fields and behaviors are stable; ticket-routing integrations can rely on t **Alert type.** `{{.Type}}` is the metric or condition that fired: `cpu`, `memory`, `disk`, `diskRead`, `diskWrite`, `networkIn`, `networkOut`, `connectivity`, and similar. The alert ID (`{{.ID}}`) is stable for the lifetime of an alert occurrence, so the `resolved` event carries the same ID as the `alert` event it closes. +**Message key.** `{{.MessageKey}}` is the stable, language-neutral condition key for rebuilding or translating a notification. Canonical alerts use `.` (for example, `metric-threshold.disk`); older alert paths fall back to `{{.Type}}`. Combine it with `{{.Event}}` and `{{.ResourceType}}` when the receiving system needs separate wording for firing/recovery events or different resource classes. Unlike a numeric message index, the symbolic key does not change when another alert type is added. + **Tenant identity.** In multi-tenant organizations and MSP client runtimes, `{{.TenantID}}` and `{{.TenantName}}` identify which tenant fired the alert. Client runtimes get identity from the `PULSE_TENANT_ID` / `PULSE_TENANT_NAME` environment; shared-process organizations stamp the org ID and display name automatically. **Resource tag routing.** Email and each alert webhook can be limited to resources with selected tags in **Alerts → Notifications**. An empty filter receives every alert. With multiple tags, choose **Match all tags** or **Match any tag**. Matching ignores case. Proxmox tags are matched as shown; Docker container and service labels are exposed as `key:value` tags (or `key` when the label value is empty). Recovery notifications follow the destinations that received the firing alert, even if a resource's tags change before recovery. @@ -192,11 +194,13 @@ stable severity/type fields from the [delivery contract](#-delivery-contract): { "event": "{{.Event}}", "alertId": "{{.ID | jsonString}}", + "messageKey": "{{.MessageKey | jsonString}}", "severity": "{{.Level | jsonString}}", "alertType": "{{.Type | jsonString}}", "tenantId": "{{.TenantID | jsonString}}", "tenantName": "{{.TenantName | jsonString}}", "resource": "{{.ResourceName | jsonString}}", + "resourceType": "{{.ResourceType | jsonString}}", "node": "{{.Node | jsonString}}", "summary": "{{.Message | jsonString}}", "value": {{.Value}}, diff --git a/frontend-modern/src/components/__tests__/whatsNewModel.test.ts b/frontend-modern/src/components/__tests__/whatsNewModel.test.ts index c95f2d10b..9caab4190 100644 --- a/frontend-modern/src/components/__tests__/whatsNewModel.test.ts +++ b/frontend-modern/src/components/__tests__/whatsNewModel.test.ts @@ -1,4 +1,6 @@ import { describe, expect, it } from 'vitest'; +import { readFileSync } from 'node:fs'; +import path from 'node:path'; import { extractChangelog, extractHighlights, isReleaseVersion } from '../whatsNewModel'; import whatsNewCardSource from '../WhatsNewCard.tsx?raw'; @@ -261,3 +263,19 @@ describe('post-update telemetry disclosure', () => { expect(whatsNewCardSource).not.toContain('setDialogVisible(true);\n markVersionSeen'); }); }); + +describe('current candidate notification packet', () => { + it('keeps translation-ready webhook support in both release summaries', () => { + const releaseNotes = readFileSync( + path.resolve(process.cwd(), '../docs/releases/RELEASE_NOTES_v6.4.0-rc.3.md'), + 'utf8', + ); + const changelog = readFileSync( + path.resolve(process.cwd(), '../docs/releases/V6_CHANGELOG_v6.4.0-rc.3.md'), + 'utf8', + ); + + expect(releaseNotes).toContain('Translation-ready webhooks'); + expect(changelog).toContain('stable language-neutral message key'); + }); +}); diff --git a/frontend-modern/src/utils/__tests__/alertWebhookPresentation.test.ts b/frontend-modern/src/utils/__tests__/alertWebhookPresentation.test.ts index 65fe9de0b..12e73d18d 100644 --- a/frontend-modern/src/utils/__tests__/alertWebhookPresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/alertWebhookPresentation.test.ts @@ -1,5 +1,7 @@ import { describe, expect, it } from 'vitest'; import { + ALERT_WEBHOOK_PAYLOAD_TEMPLATE_PLACEHOLDER, + ALERT_WEBHOOK_PAYLOAD_VARIABLES, ALERT_WEBHOOK_TEST_FAILURE, ALERT_WEBHOOK_TEST_SUCCESS, getAlertWebhookServices, @@ -15,6 +17,18 @@ import { } from '@/utils/alertWebhookPresentation'; describe('alertWebhookPresentation', () => { + it('advertises translation-ready generic webhook variables', () => { + expect(ALERT_WEBHOOK_PAYLOAD_TEMPLATE_PLACEHOLDER).toContain( + '"message_key": "{{.MessageKey}}"', + ); + expect(ALERT_WEBHOOK_PAYLOAD_TEMPLATE_PLACEHOLDER).toContain( + '"resource_type": "{{.ResourceType}}"', + ); + expect(ALERT_WEBHOOK_PAYLOAD_VARIABLES).toContain('{{.MessageKey}}'); + expect(ALERT_WEBHOOK_PAYLOAD_VARIABLES).toContain('{{.ResourceType}}'); + expect(ALERT_WEBHOOK_PAYLOAD_VARIABLES).toContain('{{.NodeDisplayName}}'); + }); + it('returns canonical webhook test-result copy', () => { expect(ALERT_WEBHOOK_TEST_SUCCESS).toBe('Test webhook sent successfully!'); expect(ALERT_WEBHOOK_TEST_FAILURE).toBe('Failed to send test webhook'); diff --git a/frontend-modern/src/utils/alertWebhookPresentation.ts b/frontend-modern/src/utils/alertWebhookPresentation.ts index 168bf932c..e41722ffd 100644 --- a/frontend-modern/src/utils/alertWebhookPresentation.ts +++ b/frontend-modern/src/utils/alertWebhookPresentation.ts @@ -112,13 +112,16 @@ export const ALERT_WEBHOOK_MENTION_HELP_LABEL = 'Optional — tag users or group export const ALERT_WEBHOOK_MENTION_FALLBACK_PLACEHOLDER = '@everyone'; export const ALERT_WEBHOOK_PAYLOAD_HELP_LABEL = 'Optional — leave empty to use default'; export const ALERT_WEBHOOK_PAYLOAD_TEMPLATE_PLACEHOLDER = `{ + "event": "{{.Event}}", + "message_key": "{{.MessageKey}}", "text": "Alert: {{.Level}} - {{.Message}}", + "resource_type": "{{.ResourceType}}", "resource": "{{.ResourceName}}", "value": {{.Value}}, "threshold": {{.Threshold}} }`; export const ALERT_WEBHOOK_PAYLOAD_VARIABLES = - '{{.ID}}, {{.Level}}, {{.Type}}, {{.ResourceName}}, {{.Node}}, {{.Message}}, {{.Value}}, {{.Threshold}}, {{.Duration}}, {{.Timestamp}}'; + '{{.ID}}, {{.Event}}, {{.MessageKey}}, {{.Level}}, {{.Type}}, {{.ResourceType}}, {{.ResourceName}}, {{.Node}}, {{.NodeDisplayName}}, {{.Message}}, {{.Value}}, {{.Threshold}}, {{.Duration}}, {{.Timestamp}}'; export const ALERT_WEBHOOK_CUSTOM_FIELDS_HELP = 'Available as'; export const ALERT_WEBHOOK_CUSTOM_FIELDS_REFERENCE = '{{.CustomFields.}}'; export const ALERT_WEBHOOK_CUSTOM_FIELD_KEY_PLACEHOLDER = 'Field name'; diff --git a/internal/notifications/notifications.go b/internal/notifications/notifications.go index d37b81f0f..d5a95031d 100644 --- a/internal/notifications/notifications.go +++ b/internal/notifications/notifications.go @@ -3052,10 +3052,17 @@ func (n *NotificationManager) prepareWebhookData(alert *alerts.Alert, customFiel } resourceType := "" + messageKey := strings.TrimSpace(alert.Type) if alert.Metadata != nil { if rt, ok := alert.Metadata["resourceType"].(string); ok { resourceType = rt } + if kind, ok := alert.Metadata["canonicalAlertKind"].(string); ok && strings.TrimSpace(kind) != "" { + messageKey = strings.TrimSpace(kind) + if alertType := strings.TrimSpace(alert.Type); alertType != "" { + messageKey += "." + alertType + } + } } var metadataCopy map[string]interface{} @@ -3079,6 +3086,7 @@ func (n *NotificationManager) prepareWebhookData(alert *alerts.Alert, customFiel return WebhookPayloadData{ ID: alert.ID, + MessageKey: messageKey, Level: string(alert.Level), Type: alert.Type, ResourceName: alert.ResourceName, diff --git a/internal/notifications/notifications_test.go b/internal/notifications/notifications_test.go index 83acc15c1..1038a0e2d 100644 --- a/internal/notifications/notifications_test.go +++ b/internal/notifications/notifications_test.go @@ -2293,6 +2293,35 @@ func TestPrepareWebhookData(t *testing.T) { } }) + t.Run("builds a language-neutral key from canonical alert identity", func(t *testing.T) { + nm := &NotificationManager{} + alert := &alerts.Alert{ + ID: "test-1", + Type: "disk", + StartTime: time.Now(), + Metadata: map[string]interface{}{ + "canonicalAlertKind": "metric-threshold", + }, + } + + result := nm.prepareWebhookData(alert, nil) + + if result.MessageKey != "metric-threshold.disk" { + t.Fatalf("expected canonical message key, got %q", result.MessageKey) + } + }) + + t.Run("uses alert type as the message key for legacy alert paths", func(t *testing.T) { + nm := &NotificationManager{} + alert := &alerts.Alert{ID: "test-1", Type: "connectivity", StartTime: time.Now()} + + result := nm.prepareWebhookData(alert, nil) + + if result.MessageKey != "connectivity" { + t.Fatalf("expected legacy message key, got %q", result.MessageKey) + } + }) + t.Run("resourceType empty when not in metadata", func(t *testing.T) { nm := &NotificationManager{} alert := &alerts.Alert{ diff --git a/internal/notifications/templates_test.go b/internal/notifications/templates_test.go index 0d0f527f8..0357652db 100644 --- a/internal/notifications/templates_test.go +++ b/internal/notifications/templates_test.go @@ -438,6 +438,21 @@ func TestGetWebhookTemplates_GenericSettings(t *testing.T) { if generic.Method != "POST" { t.Errorf("Generic Method = %q, want POST", generic.Method) } + for _, field := range []string{ + `"event": "{{.Event | jsonString}}"`, + `"message_key": "{{.MessageKey | jsonString}}"`, + `"resource_type": "{{.ResourceType | jsonString}}"`, + `"node_display_name": "{{.NodeDisplayName | jsonString}}"`, + `"value_formatted": "{{.ValueFormatted | jsonString}}"`, + `"threshold_formatted": "{{.ThresholdFormatted | jsonString}}"`, + } { + if !strings.Contains(generic.PayloadTemplate, field) { + t.Errorf("Generic PayloadTemplate missing %s", field) + } + } + if !strings.Contains(generic.ResolvedPayloadTemplate, `"message_key": "{{.MessageKey | jsonString}}"`) { + t.Error("Generic ResolvedPayloadTemplate missing message_key") + } } func TestGetWebhookTemplates_PayloadTemplatesHaveRequiredFields(t *testing.T) { diff --git a/internal/notifications/webhook_enhanced.go b/internal/notifications/webhook_enhanced.go index a41e897d0..3f6902cf4 100644 --- a/internal/notifications/webhook_enhanced.go +++ b/internal/notifications/webhook_enhanced.go @@ -40,6 +40,7 @@ type WebhookFilterRules struct { type WebhookPayloadData struct { // Alert fields ID string + MessageKey string Level string Type string ResourceName string diff --git a/internal/notifications/webhook_templates.go b/internal/notifications/webhook_templates.go index 93ab8424e..8c9f8db4f 100644 --- a/internal/notifications/webhook_templates.go +++ b/internal/notifications/webhook_templates.go @@ -534,15 +534,21 @@ View in Pulse: {{.Instance}}`, Method: "POST", Headers: map[string]string{"Content-Type": "application/json"}, PayloadTemplate: `{ + "event": "{{.Event | jsonString}}", "alert": { "id": "{{.ID | jsonString}}", + "message_key": "{{.MessageKey | jsonString}}", "level": "{{.Level | jsonString}}", "type": "{{.Type | jsonString}}", + "resource_type": "{{.ResourceType | jsonString}}", "resource_name": "{{.ResourceName | jsonString}}", "node": "{{.Node | jsonString}}", + "node_display_name": "{{.NodeDisplayName | jsonString}}", "message": "{{.Message | jsonString}}", "value": {{.Value}}, + "value_formatted": "{{.ValueFormatted | jsonString}}", "threshold": {{.Threshold}}, + "threshold_formatted": "{{.ThresholdFormatted | jsonString}}", "start_time": "{{.StartTime | jsonString}}", "duration": "{{.Duration | jsonString}}" },{{if .TenantID}} @@ -557,9 +563,12 @@ View in Pulse: {{.Instance}}`, "event": "resolved", "alert": { "id": "{{.ID | jsonString}}", + "message_key": "{{.MessageKey | jsonString}}", "type": "{{.Type | jsonString}}", + "resource_type": "{{.ResourceType | jsonString}}", "resource_name": "{{.ResourceName | jsonString}}", "node": "{{.Node | jsonString}}", + "node_display_name": "{{.NodeDisplayName | jsonString}}", "message": "{{.Message | jsonString}}", "start_time": "{{.StartTime | jsonString}}", "duration": "{{.Duration | jsonString}}"