package notifications // WebhookTemplate represents a webhook template for popular services type WebhookTemplate struct { Service string `json:"service"` Label string `json:"label"` MentionPlaceholder string `json:"mentionPlaceholder"` MentionHelp string `json:"mentionHelp"` Name string `json:"name"` Description string `json:"description"` URLPattern string `json:"urlPattern"` Method string `json:"method"` Headers map[string]string `json:"headers"` PayloadTemplate string `json:"payloadTemplate"` ResolvedPayloadTemplate string `json:"resolvedPayloadTemplate"` Instructions string `json:"instructions"` } // GetWebhookTemplates returns templates for popular webhook services func GetWebhookTemplates() []WebhookTemplate { return []WebhookTemplate{ { Service: "discord", Label: "Discord", MentionPlaceholder: "@everyone or <@USER_ID> or <@&ROLE_ID>", MentionHelp: "Discord: Use @everyone, @here, <@USER_ID>, or <@&ROLE_ID>", Name: "Discord Webhook", Description: "Discord server webhook", URLPattern: "https://discord.com/api/webhooks/{webhook_id}/{webhook_token}", Method: "POST", Headers: map[string]string{"Content-Type": "application/json"}, PayloadTemplate: `{ "username": "Pulse Monitoring", {{if .Mention}}"content": "{{.Mention | jsonString}}",{{end}} "embeds": [{ "title": "Pulse Alert: {{.Level | title | jsonString}}", "description": "{{.Message | jsonString}}", "color": {{if eq .Level "critical"}}15158332{{else if eq .Level "warning"}}15105570{{else}}3447003{{end}}, "fields": [ {"name": "Resource", "value": "{{.ResourceName | jsonString}}", "inline": true}, {"name": "Node", "value": "{{.Node | jsonString}}", "inline": true}, {"name": "Type", "value": "{{.Type | title | jsonString}}", "inline": true}, {"name": "Value", "value": "{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}", "inline": true}, {"name": "Threshold", "value": "{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}", "inline": true}, {"name": "Duration", "value": "{{.Duration | jsonString}}", "inline": true} ], "timestamp": "{{.Timestamp | jsonString}}", "footer": { "text": "Pulse Monitoring" } }] }`, ResolvedPayloadTemplate: `{ "username": "Pulse Monitoring", "embeds": [{ "title": "Resolved: {{.ResourceName | jsonString}}", "description": "{{.Message | jsonString}}", "color": 3066993, "fields": [ {"name": "Resource", "value": "{{.ResourceName | jsonString}}", "inline": true}, {"name": "Node", "value": "{{.Node | jsonString}}", "inline": true}, {"name": "Type", "value": "{{.Type | title | jsonString}}", "inline": true}, {"name": "Duration", "value": "{{.Duration | jsonString}}", "inline": true}, {"name": "Resolved At", "value": "{{.ResolvedAt | jsonString}}", "inline": true} ], "timestamp": "{{.Timestamp | jsonString}}", "footer": { "text": "Pulse Monitoring" } }] }`, Instructions: "1. In Discord, go to Server Settings > Integrations > Webhooks\n2. Create a new webhook and copy the URL\n3. Paste the URL here (format: https://discord.com/api/webhooks/...)\n4. Optional: Add a mention in the Mention field (e.g., @everyone, <@USER_ID>, <@&ROLE_ID>)", }, { Service: "telegram", Label: "Telegram", Name: "Telegram Bot", Description: "Telegram bot notifications", URLPattern: "https://api.telegram.org/bot{bot_token}/sendMessage", Method: "POST", Headers: map[string]string{"Content-Type": "application/json"}, PayloadTemplate: `{ "chat_id": "{{.ChatID | jsonString}}", "text": "*Pulse Alert: {{.Level | title | jsonString}}*\n\n{{.Message | jsonString}}\n\n*Details:*\n• Resource: {{.ResourceName | jsonString}}\n• Node: {{.Node | jsonString}}\n• Type: {{.Type | title | jsonString}}\n• Value: {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}\n• Threshold: {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}\n• Duration: {{.Duration | jsonString}}\n\n[View in Pulse]({{.Instance | jsonString}})", "parse_mode": "Markdown", "disable_web_page_preview": true }`, ResolvedPayloadTemplate: `{ "chat_id": "{{.ChatID | jsonString}}", "text": "*Resolved: {{.ResourceName | jsonString}}*\n\n{{.Message | jsonString}}\n\n*Details:*\n• Resource: {{.ResourceName | jsonString}}\n• Node: {{.Node | jsonString}}\n• Type: {{.Type | title | jsonString}}\n• Duration: {{.Duration | jsonString}}\n• Resolved At: {{.ResolvedAt | jsonString}}", "parse_mode": "Markdown", "disable_web_page_preview": true }`, Instructions: "1. Create a bot with @BotFather on Telegram\n2. Get your bot token\n3. Get your chat ID by messaging the bot and visiting: https://api.telegram.org/bot/getUpdates\n4. URL format: https://api.telegram.org/bot/sendMessage?chat_id=\n5. IMPORTANT: You MUST include ?chat_id=YOUR_CHAT_ID in the URL", }, { Service: "slack", Label: "Slack", MentionPlaceholder: "@channel, @here, or <@USER_ID>", MentionHelp: "Slack: Use @channel, @here, <@USER_ID>, or ", Name: "Slack Incoming Webhook", Description: "Slack incoming webhook", URLPattern: "https://hooks.slack.com/services/{webhook_path}", Method: "POST", Headers: map[string]string{"Content-Type": "application/json"}, PayloadTemplate: `{ "text": "{{if .Mention}}{{.Mention | jsonString}} {{end}}Pulse Alert: {{.Level | title | jsonString}} - {{.ResourceName | jsonString}}", "blocks": [ {{if .Mention}}{ "type": "section", "text": { "type": "mrkdwn", "text": "{{.Mention | jsonString}}" } },{{end}} { "type": "header", "text": { "type": "plain_text", "text": "Pulse Alert: {{.Level | title | jsonString}}", "emoji": true } }, { "type": "section", "text": { "type": "mrkdwn", "text": "{{.Message | jsonString}}" } }, { "type": "section", "fields": [ {"type": "mrkdwn", "text": "*Resource:*\n{{.ResourceName | jsonString}}"}, {"type": "mrkdwn", "text": "*Node:*\n{{.Node | jsonString}}"}, {"type": "mrkdwn", "text": "*Type:*\n{{.Type | title | jsonString}}"}, {"type": "mrkdwn", "text": "*Value:*\n{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}"}, {"type": "mrkdwn", "text": "*Threshold:*\n{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}"}, {"type": "mrkdwn", "text": "*Duration:*\n{{.Duration | jsonString}}"} ] }, { "type": "context", "elements": [ { "type": "mrkdwn", "text": "View in <{{.Instance | jsonString}}|Proxmox> | Alert Identifier: {{.ID | jsonString}}" } ] } ] }`, ResolvedPayloadTemplate: `{ "text": "Resolved: {{.ResourceName | jsonString}}", "blocks": [ { "type": "header", "text": { "type": "plain_text", "text": "Resolved: {{.ResourceName | jsonString}}", "emoji": true } }, { "type": "section", "text": { "type": "mrkdwn", "text": "{{.Message | jsonString}}" } }, { "type": "section", "fields": [ {"type": "mrkdwn", "text": "*Resource:*\n{{.ResourceName | jsonString}}"}, {"type": "mrkdwn", "text": "*Node:*\n{{.Node | jsonString}}"}, {"type": "mrkdwn", "text": "*Type:*\n{{.Type | title | jsonString}}"}, {"type": "mrkdwn", "text": "*Duration:*\n{{.Duration | jsonString}}"}, {"type": "mrkdwn", "text": "*Resolved At:*\n{{.ResolvedAt | jsonString}}"} ] }, { "type": "context", "elements": [ { "type": "mrkdwn", "text": "Alert Identifier: {{.ID | jsonString}}" } ] } ] }`, Instructions: "1. In Slack, go to Apps > Incoming Webhooks\n2. Add to Slack and choose a channel\n3. Copy the webhook URL and paste it here (format: https://hooks.slack.com/services/...)\n4. Optional: Add a mention in the Mention field (e.g., @channel, @here, <@USER_ID>, )", }, { Service: "teams", Label: "Microsoft Teams", MentionPlaceholder: "@General or user email", MentionHelp: "Teams: Use channel names like @General", Name: "Microsoft Teams", Description: "Microsoft Teams webhook", URLPattern: "https://{tenant}.webhook.office.com/webhookb2/{webhook_path}", Method: "POST", Headers: map[string]string{"Content-Type": "application/json"}, PayloadTemplate: `{ "@type": "MessageCard", "@context": "http://schema.org/extensions", "themeColor": "{{if eq .Level "critical"}}FF0000{{else if eq .Level "warning"}}FFA500{{else}}00FF00{{end}}", "summary": "Pulse Alert: {{.Level | title | jsonString}} - {{.ResourceName | jsonString}}", {{if .Mention}}"text": "{{.Mention | jsonString}}",{{end}} "sections": [{ "activityTitle": "Pulse Alert: {{.Level | title | jsonString}}", "activitySubtitle": "{{.Message | jsonString}}", "facts": [ {"name": "Resource", "value": "{{.ResourceName | jsonString}}"}, {"name": "Node", "value": "{{.Node | jsonString}}"}, {"name": "Type", "value": "{{.Type | title | jsonString}}"}, {"name": "Value", "value": "{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}"}, {"name": "Threshold", "value": "{{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}"}, {"name": "Duration", "value": "{{.Duration | jsonString}}"}, {"name": "Instance", "value": "{{.Instance | jsonString}}"} ], "markdown": true }], "potentialAction": [{ "@type": "OpenUri", "name": "View in Proxmox", "targets": [{ "os": "default", "uri": "{{.Instance | jsonString}}" }] }] }`, ResolvedPayloadTemplate: `{ "@type": "MessageCard", "@context": "http://schema.org/extensions", "themeColor": "2DC72D", "summary": "Resolved: {{.ResourceName | jsonString}}", "sections": [{ "activityTitle": "Resolved: {{.ResourceName | jsonString}}", "activitySubtitle": "{{.Message | jsonString}}", "facts": [ {"name": "Resource", "value": "{{.ResourceName | jsonString}}"}, {"name": "Node", "value": "{{.Node | jsonString}}"}, {"name": "Type", "value": "{{.Type | title | jsonString}}"}, {"name": "Duration", "value": "{{.Duration | jsonString}}"}, {"name": "Resolved At", "value": "{{.ResolvedAt | jsonString}}"} ], "markdown": true }] }`, Instructions: "1. In Teams channel, click ... > Connectors\n2. Configure Incoming Webhook\n3. Copy the URL and paste it here\n4. Optional: Add a mention in the Mention field (e.g., @General)\n\nNote: MessageCard format is supported until December 2025. For new implementations, consider using Adaptive Cards.", }, { Service: "pagerduty", Label: "PagerDuty", Name: "PagerDuty Events API v2", Description: "PagerDuty Events API v2", URLPattern: "https://events.pagerduty.com/v2/enqueue", Method: "POST", Headers: map[string]string{ "Content-Type": "application/json", "Accept": "application/vnd.pagerduty+json;version=2", }, PayloadTemplate: `{ "routing_key": "{{.CustomFields.routing_key | jsonString}}", "event_action": "trigger", "dedup_key": "{{.ID | jsonString}}", "payload": { "summary": "{{.Message | jsonString}}", "timestamp": "{{.Timestamp | jsonString}}", "severity": "{{if eq .Level "critical"}}critical{{else if eq .Level "warning"}}warning{{else}}info{{end}}", "source": "{{.Node | jsonString}}", "component": "{{.ResourceName | jsonString}}", "group": "{{.Type | jsonString}}", "class": "{{.Type | jsonString}}", "custom_details": { "alert_identifier": "{{.ID | jsonString}}", "resource_type": "{{.Type | jsonString}}", "current_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 | jsonString}}", "instance": "{{.Instance | jsonString}}" } }, "client": "Pulse Monitoring", "client_url": "{{.Instance | jsonString}}", "links": [{ "href": "{{.Instance | jsonString}}", "text": "View in Proxmox" }] }`, ResolvedPayloadTemplate: `{ "routing_key": "{{.CustomFields.routing_key | jsonString}}", "event_action": "resolve", "dedup_key": "{{.ID | jsonString}}" }`, Instructions: "1. In PagerDuty, go to Configuration > Services\n2. Add an integration > Events API V2\n3. Copy the Integration Key\n4. Add the key as a custom field named 'routing_key'\n\nNote: PagerDuty recommends using Events API v2 for new integrations.", }, { Service: "teams-adaptive", Label: "Teams (Adaptive)", MentionPlaceholder: "@General or user email", MentionHelp: "Teams: Use channel names like @General", Name: "Microsoft Teams (Adaptive Card)", Description: "Teams with Adaptive Cards", URLPattern: "https://{tenant}.webhook.office.com/webhookb2/{webhook_path}", Method: "POST", Headers: map[string]string{"Content-Type": "application/json"}, PayloadTemplate: `{ "type": "message", "attachments": [{ "contentType": "application/vnd.microsoft.card.adaptive", "content": { "type": "AdaptiveCard", "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.4", "body": [ { "type": "TextBlock", "text": "Pulse Alert: {{.Level | title}}", "weight": "Bolder", "size": "Large", "color": "{{if eq .Level "critical"}}Attention{{else if eq .Level "warning"}}Warning{{else}}Good{{end}}" }, { "type": "TextBlock", "text": "{{.Message | jsonString}}", "wrap": true, "spacing": "Small" }, { "type": "FactSet", "facts": [ {"title": "Resource", "value": "{{.ResourceName | jsonString}}"}, {"title": "Node", "value": "{{.Node | jsonString}}"}, {"title": "Type", "value": "{{.Type | title | jsonString}}"}, {"title": "Current Value", "value": "{{printf "%.1f" .Value}}%"}, {"title": "Threshold", "value": "{{printf "%.0f" .Threshold}}%"}, {"title": "Duration", "value": "{{.Duration | jsonString}}"}, {"title": "Alert Identifier", "value": "{{.ID | jsonString}}"} ] } ], "actions": [{ "type": "Action.OpenUrl", "title": "View in Proxmox", "url": "{{.Instance | jsonString}}" }] } }] }`, ResolvedPayloadTemplate: `{ "type": "message", "attachments": [{ "contentType": "application/vnd.microsoft.card.adaptive", "content": { "type": "AdaptiveCard", "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.4", "body": [ { "type": "TextBlock", "text": "Resolved: {{.ResourceName}}", "weight": "Bolder", "size": "Large", "color": "Good" }, { "type": "TextBlock", "text": "{{.Message | jsonString}}", "wrap": true, "spacing": "Small" }, { "type": "FactSet", "facts": [ {"title": "Resource", "value": "{{.ResourceName | jsonString}}"}, {"title": "Node", "value": "{{.Node | jsonString}}"}, {"title": "Type", "value": "{{.Type | title | jsonString}}"}, {"title": "Duration", "value": "{{.Duration | jsonString}}"}, {"title": "Resolved At", "value": "{{.ResolvedAt | jsonString}}"}, {"title": "Alert Identifier", "value": "{{.ID | jsonString}}"} ] } ] } }] }`, Instructions: "1. In Teams channel, click ... > Connectors\n2. Configure Incoming Webhook\n3. Copy the URL and paste it here\n\nThis uses the modern Adaptive Card format recommended for new implementations.", }, { Service: "pushover", Label: "Pushover", Name: "Pushover", Description: "Mobile push notifications", URLPattern: "https://api.pushover.net/1/messages.json", Method: "POST", Headers: map[string]string{"Content-Type": "application/json"}, PayloadTemplate: `{ "token": "{{.CustomFields.token | jsonString}}", "user": "{{.CustomFields.user | jsonString}}", "title": "Pulse Alert: {{.Level | title | jsonString}} - {{.ResourceName | jsonString}}", "message": "{{.Message | jsonString}}\n\n• Resource: {{.ResourceName | jsonString}}\n• Node: {{.Node | jsonString}}\n• Type: {{.Type | title | jsonString}}\n• Value: {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}\n• Threshold: {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}\n• Duration: {{.Duration | jsonString}}", "priority": {{if .CustomFields.priority}}{{.CustomFields.priority}}{{else}}{{if eq .Level "critical"}}1{{else if eq .Level "warning"}}0{{else}}-1{{end}}{{end}}, "sound": "{{if .CustomFields.sound}}{{.CustomFields.sound | jsonString}}{{else}}{{if eq .Level "critical"}}siren{{else if eq .Level "warning"}}tugboat{{else}}pushover{{end}}{{end}}", "device": "{{if .CustomFields.device}}{{.CustomFields.device | jsonString}}{{else}}{{.ResourceName | jsonString}}{{end}}", "timestamp": "{{.Timestamp | jsonString}}" }`, ResolvedPayloadTemplate: `{ "token": "{{.CustomFields.token | jsonString}}", "user": "{{.CustomFields.user | jsonString}}", "title": "Resolved: {{.ResourceName | jsonString}}", "message": "{{.Message | jsonString}}\n\n• Resource: {{.ResourceName | jsonString}}\n• Node: {{.Node | jsonString}}\n• Type: {{.Type | title | jsonString}}\n• Duration: {{.Duration | jsonString}}\n• Resolved At: {{.ResolvedAt | jsonString}}", "priority": -1, "sound": "pushover", "timestamp": "{{.Timestamp | jsonString}}" }`, Instructions: "1. Create an application at https://pushover.net/apps\n2. Copy your Application Token\n3. Get your User Key from your Pushover dashboard\n4. URL: https://api.pushover.net/1/messages.json\n5. Add custom fields:\n • token: YOUR_APP_TOKEN (required)\n • user: YOUR_USER_KEY (required)\n • sound: notification sound (optional, e.g., spacealarm, siren, tugboat)\n • priority: -2 to 2 (optional, overrides level-based default)\n • device: specific device name (optional, overrides ResourceName)", }, { Service: "gotify", Label: "Gotify", Name: "Gotify", Description: "Self-hosted push notifications", URLPattern: "https://{your-gotify-server}/message?token={your-app-token}", Method: "POST", Headers: map[string]string{"Content-Type": "application/json"}, PayloadTemplate: `{ "message": "**{{if eq .Level "critical"}}CRITICAL{{else if eq .Level "warning"}}WARNING{{else}}INFO{{end}}**: **{{.ResourceName | jsonString}}** on **{{.Node | jsonString}}**\n\n{{.Message | jsonString}}\n\n**Alert Details:**\n- **Resource:** {{.ResourceName | jsonString}}\n- **Node:** {{.Node | jsonString}}\n- **Type:** {{.Type | title | jsonString}}\n- **Current:** {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}}\n- **Threshold:** {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}}\n- **Duration:** {{.Duration | jsonString}}\n- **Alert Identifier:** {{.ID | jsonString}}\n\n[View in Pulse]({{.Instance | jsonString}})", "title": "{{.ResourceName | jsonString}} - {{.Type | title | jsonString}} {{.Level | upper | jsonString}} Alert", "priority": {{if eq .Level "critical"}}10{{else if eq .Level "warning"}}5{{else}}2{{end}}, "extras": { "client::display": { "contentType": "text/markdown" }, "pulse::alert": { "id": "{{.ID | jsonString}}", "level": "{{.Level | jsonString}}", "type": "{{.Type | jsonString}}", "resource_name": "{{.ResourceName | jsonString}}", "node": "{{.Node | jsonString}}", "value": {{.Value}}, "threshold": {{.Threshold}}, "duration": "{{.Duration | jsonString}}", "instance": "{{.Instance | jsonString}}" } } }`, ResolvedPayloadTemplate: `{ "message": "**RESOLVED**: **{{.ResourceName | jsonString}}** on **{{.Node | jsonString}}**\n\n{{.Message | jsonString}}\n\n**Details:**\n- **Resource:** {{.ResourceName | jsonString}}\n- **Node:** {{.Node | jsonString}}\n- **Type:** {{.Type | title | jsonString}}\n- **Duration:** {{.Duration | jsonString}}\n- **Resolved At:** {{.ResolvedAt | jsonString}}\n- **Alert Identifier:** {{.ID | jsonString}}", "title": "Resolved: {{.ResourceName | jsonString}} - {{.Type | title | jsonString}}", "priority": 2, "extras": { "client::display": { "contentType": "text/markdown" }, "pulse::alert": { "id": "{{.ID | jsonString}}", "event": "resolved", "resource_name": "{{.ResourceName | jsonString}}", "node": "{{.Node | jsonString}}", "duration": "{{.Duration | jsonString}}", "resolved_at": "{{.ResolvedAt | jsonString}}" } } }`, Instructions: "1. In Gotify, create a new application\n2. Copy the application token\n3. URL format: https://your-gotify-server/message?token=YOUR_APP_TOKEN\n4. The token must be included in the URL as a parameter", }, { Service: "ntfy", Label: "ntfy", Name: "ntfy.sh", Description: "Push notifications via ntfy.sh", URLPattern: "https://ntfy.sh/{topic}", Method: "POST", Headers: map[string]string{ "Content-Type": "text/plain", // Note: Title, Priority, and Tags headers should be added dynamically based on alert level // For now, we'll use static reasonable defaults that won't break }, PayloadTemplate: `{{if eq .Level "critical"}}CRITICAL{{else if eq .Level "warning"}}WARNING{{else}}INFO{{end}}: {{.ResourceName}} on {{.Node}} {{.Message}} Alert Details: - Resource: {{.ResourceName}} - Node: {{.Node}} - Type: {{.Type | title}} - Current: {{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}} - Alert Identifier: {{.ID}} 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. For authentication, add a custom header:\n • Header Name: Authorization\n • Header Value: Bearer YOUR_TOKEN (or Basic base64_encoded_credentials)\n4. Subscribe to the topic in your ntfy app using the same topic name", }, { Service: "mattermost", Label: "Mattermost", MentionPlaceholder: "@channel, @all, or @username", MentionHelp: "Mattermost: Use @channel, @all, or @username", Name: "Mattermost Incoming Webhook", Description: "Mattermost incoming webhook", URLPattern: "https://{your-mattermost-server}/hooks/{webhook_id}", Method: "POST", Headers: map[string]string{"Content-Type": "application/json"}, PayloadTemplate: `{ "username": "Pulse Monitoring", "icon_url": "https://raw.githubusercontent.com/rcourtman/Pulse/main/frontend-modern/public/android-chrome-192x192.png", "text": "{{if eq .Level "critical"}}:rotating_light: **CRITICAL ALERT**{{else if eq .Level "warning"}}:warning: **WARNING ALERT**{{else}}:information_source: **INFO**{{end}}\n\n**{{.ResourceName | jsonString}}** on **{{.Node | jsonString}}**\n\n{{.Message | jsonString}}\n\n| Detail | Value |\n|:-------|:------|\n| Resource | {{.ResourceName | jsonString}} |\n| Node | {{.Node | jsonString}} |\n| Type | {{.Type | title | jsonString}} |\n| Current | {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.1f" .Value}} MB/s{{else}}{{printf "%.1f" .Value}}%{{end}} |\n| Threshold | {{if or (eq .Type "diskRead") (eq .Type "diskWrite")}}{{printf "%.0f" .Threshold}} MB/s{{else}}{{printf "%.0f" .Threshold}}%{{end}} |\n| Duration | {{.Duration | jsonString}} |\n| Alert Identifier | {{.ID | jsonString}} |\n\n[View in Pulse]({{.Instance | jsonString}})" }`, ResolvedPayloadTemplate: `{ "username": "Pulse Monitoring", "icon_url": "https://raw.githubusercontent.com/rcourtman/Pulse/main/frontend-modern/public/android-chrome-192x192.png", "text": ":white_check_mark: **RESOLVED**\n\n**{{.ResourceName | jsonString}}** on **{{.Node | jsonString}}**\n\n{{.Message | jsonString}}\n\n| Detail | Value |\n|:-------|:------|\n| Resource | {{.ResourceName | jsonString}} |\n| Node | {{.Node | jsonString}} |\n| Type | {{.Type | title | jsonString}} |\n| Duration | {{.Duration | jsonString}} |\n| Resolved At | {{.ResolvedAt | jsonString}} |\n| Alert Identifier | {{.ID | jsonString}} |" }`, Instructions: "1. In Mattermost, go to Integrations > Incoming Webhooks\n2. Create a new webhook and select the channel\n3. Copy the webhook URL and paste it here\n\nNote: This template uses Markdown formatting which is fully supported by Mattermost.", }, { Service: "generic", Label: "Generic", Name: "Generic JSON Webhook", Description: "Custom webhook endpoint", URLPattern: "", Method: "POST", Headers: map[string]string{"Content-Type": "application/json"}, PayloadTemplate: `{ "alert": { "id": "{{.ID | jsonString}}", "level": "{{.Level | jsonString}}", "type": "{{.Type | jsonString}}", "resource_name": "{{.ResourceName | jsonString}}", "node": "{{.Node | jsonString}}", "message": "{{.Message | jsonString}}", "value": {{.Value}}, "threshold": {{.Threshold}}, "start_time": "{{.StartTime | jsonString}}", "duration": "{{.Duration | jsonString}}" }, "timestamp": "{{.Timestamp | jsonString}}", "source": "pulse-monitoring" }`, ResolvedPayloadTemplate: `{ "event": "resolved", "alert": { "id": "{{.ID | jsonString}}", "type": "{{.Type | jsonString}}", "resource_name": "{{.ResourceName | jsonString}}", "node": "{{.Node | jsonString}}", "message": "{{.Message | jsonString}}", "start_time": "{{.StartTime | jsonString}}", "duration": "{{.Duration | jsonString}}" }, "resolved_at": "{{.ResolvedAt | jsonString}}", "timestamp": "{{.Timestamp | jsonString}}", "source": "pulse-monitoring" }`, Instructions: "Configure with your custom webhook endpoint", }, } }