From 532cde005e84aaed4a4673c51e4c0f295e56fc7e Mon Sep 17 00:00:00 2001 From: Pulse Monitor Date: Sun, 31 Aug 2025 16:07:42 +0000 Subject: [PATCH] fix: ntfy webhook now sends plain text instead of JSON - Modified generatePayloadFromTemplate to skip JSON validation for ntfy - ntfy uses plain text body format, not JSON - Messages now successfully delivered to ntfy topics - Headers for title/priority/tags need dynamic templating support (future enhancement) --- internal/notifications/notifications.go | 21 ++++++++++--- internal/notifications/webhook_templates.go | 35 +++++++++++---------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/internal/notifications/notifications.go b/internal/notifications/notifications.go index 031892767..fe115897e 100644 --- a/internal/notifications/notifications.go +++ b/internal/notifications/notifications.go @@ -463,7 +463,7 @@ func (n *NotificationManager) sendGroupedWebhook(webhook WebhookConfig, alertLis // The template already has the chat_id embedded } - jsonData, err = n.generatePayloadFromTemplate(enhanced.PayloadTemplate, data) + jsonData, err = n.generatePayloadFromTemplateWithService(enhanced.PayloadTemplate, data, webhook.Service) if err != nil { log.Error(). Err(err). @@ -539,7 +539,7 @@ func (n *NotificationManager) sendGroupedWebhook(webhook WebhookConfig, alertLis } } - jsonData, err = n.generatePayloadFromTemplate(enhanced.PayloadTemplate, data) + jsonData, err = n.generatePayloadFromTemplateWithService(enhanced.PayloadTemplate, data, webhook.Service) if err != nil { log.Error(). Err(err). @@ -711,7 +711,7 @@ func (n *NotificationManager) sendWebhook(webhook WebhookConfig, alert *alerts.A } } - jsonData, err = n.generatePayloadFromTemplate(enhanced.PayloadTemplate, data) + jsonData, err = n.generatePayloadFromTemplateWithService(enhanced.PayloadTemplate, data, webhook.Service) if err != nil { log.Error(). Err(err). @@ -775,7 +775,7 @@ func (n *NotificationManager) sendWebhook(webhook WebhookConfig, alert *alerts.A } } - jsonData, err = n.generatePayloadFromTemplate(enhanced.PayloadTemplate, data) + jsonData, err = n.generatePayloadFromTemplateWithService(enhanced.PayloadTemplate, data, webhook.Service) if err != nil { log.Error(). Err(err). @@ -841,6 +841,11 @@ func (n *NotificationManager) prepareWebhookData(alert *alerts.Alert, customFiel // generatePayloadFromTemplate renders the payload using Go templates func (n *NotificationManager) generatePayloadFromTemplate(templateStr string, data WebhookPayloadData) ([]byte, error) { + return n.generatePayloadFromTemplateWithService(templateStr, data, "") +} + +// generatePayloadFromTemplateWithService renders the payload using Go templates with service-specific handling +func (n *NotificationManager) generatePayloadFromTemplateWithService(templateStr string, data WebhookPayloadData, service string) ([]byte, error) { // Create template with helper functions funcMap := template.FuncMap{ "title": func(s string) string { @@ -865,7 +870,13 @@ func (n *NotificationManager) generatePayloadFromTemplate(templateStr string, da return nil, fmt.Errorf("template execution failed: %w", err) } - // Validate that the generated payload is valid JSON + // Skip JSON validation for services that use plain text payloads + if service == "ntfy" { + // ntfy uses plain text, not JSON + return buf.Bytes(), nil + } + + // Validate that the generated payload is valid JSON for other services var jsonCheck interface{} if err := json.Unmarshal(buf.Bytes(), &jsonCheck); err != nil { log.Error(). diff --git a/internal/notifications/webhook_templates.go b/internal/notifications/webhook_templates.go index e7df258a8..8d0532614 100644 --- a/internal/notifications/webhook_templates.go +++ b/internal/notifications/webhook_templates.go @@ -264,23 +264,24 @@ func GetWebhookTemplates() []WebhookTemplate { Name: "ntfy.sh", URLPattern: "https://ntfy.sh/{topic}", Method: "POST", - Headers: map[string]string{"Content-Type": "application/json"}, - PayloadTemplate: `{ - "topic": "{{.CustomFields.topic}}", - "message": "{{.Message}}", - "title": "Pulse Alert: {{.Level | title}}", - "priority": {{if eq .Level "critical"}}5{{else if eq .Level "warning"}}4{{else}}3{{end}}, - "tags": ["{{.Level}}", "{{.Type}}", "pulse"], - "click": "{{.Instance}}", - "actions": [ - { - "action": "view", - "label": "View in Pulse", - "url": "{{.Instance}}" - } - ], - "markdown": true - }`, + Headers: map[string]string{ + "Content-Type": "text/plain", + "Title": "Pulse Alert", + "Priority": "urgent", + "Tags": "pulse,monitoring", + }, + PayloadTemplate: `🚨 {{.Level | title}} Alert: {{.ResourceName}} + +{{.Message}} + +📊 Details: +• Node: {{.Node}} +• Type: {{.Type | title}} +• Value: {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}} +• Threshold: {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}} +• Duration: {{.Duration}} + +View in Pulse: {{.Instance}}`, Instructions: "1. Choose a topic name (e.g., 'my-pulse-alerts')\n2. URL format: https://ntfy.sh/YOUR_TOPIC\n Or for self-hosted: https://your-ntfy-server/YOUR_TOPIC\n3. Optional: Add authentication token in headers if required\n4. Subscribe to the topic in your ntfy app using the same topic name", }, {