Notification Templates
Customize notification payloads with Go templates for Discord, Slack, Microsoft Teams, PagerDuty, and other webhook consumers.
Templates let you customize the JSON payload that Planekeeper sends to your webhook endpoints. Use templates to format messages for specific platforms like Discord, Slack, Microsoft Teams, or PagerDuty.
When no template is configured, Planekeeper sends a standard JSON payload that works with generic webhook consumers.
Template syntax
Templates use Go template syntax with double-brace placeholders:
{{ .Variable }}
Dot notation accesses nested fields. Pipe functions transform values:
{{ .Alert.Severity | upper }} outputs: CRITICAL
{{ .Event | lower }} outputs: alert.created
{{ .Alert | json }} outputs: {"id":1,"severity":"critical",...}
Available functions
| Function | Description | Example |
|---|---|---|
upper | Convert to uppercase | {{ .Alert.Severity | upper }} |
lower | Convert to lowercase | {{ .Event | lower }} |
json | JSON-encode a value | {{ .Alert | json }} |
Template categories
Templates are organized into three categories, each covering specific event types:
| Category | Events covered | Use for |
|---|---|---|
| New alert | alert.created, alert.escalated | Initial notifications and severity escalations |
| Acknowledged | alert.acknowledged, alert.unacknowledged | Acknowledgment state changes |
| Resolved | alert.resolved | Alert resolution notifications |
Available variables
Common variables (all categories)
These variables are available in every template:
| Variable | Description |
|---|---|
{{ .IdempotencyKey }} | Unique ID for this delivery, stable across retries |
{{ .Event }} | The event type (e.g., alert.created) |
{{ .Timestamp }} | ISO 8601 timestamp |
{{ .Alert.ID }} | Alert ID |
{{ .Alert.ConfigName }} | Name of the alert configuration |
{{ .Alert.RuleName }} | Name of the monitoring rule |
{{ .Alert.RuleType }} | Rule type: days_behind, majors_behind, or minors_behind |
{{ .Alert.Severity }} | Severity level: critical, high, or moderate |
{{ .Alert.DiscoveredVersion }} | The deployed version found by the scrape job |
{{ .Alert.LatestVersion }} | The latest upstream version from the gather job |
{{ .Alert.BehindBy }} | How far behind (number of days, majors, or minors) |
{{ .Alert.ArtifactName }} | Upstream artifact name (e.g., kubernetes/kubernetes) |
{{ .Alert.RepositoryURL }} | Repository URL from the scrape job |
{{ .Alert.TargetFile }} | Target file path from the scrape job |
New alert variables
Available only in the new_alert template category:
| Variable | Description |
|---|---|
{{ .AcknowledgeURL }} | One-click acknowledgment callback URL |
{{ .PreviousSeverity }} | Previous severity before escalation (escalated events only) |
Acknowledged variables
Available only in the acknowledged template category:
| Variable | Description |
|---|---|
{{ .IsAcknowledged }} | true for acknowledged, false for unacknowledged |
{{ .AcknowledgedBy }} | Email or identifier of the person who acknowledged |
{{ .AcknowledgedAt }} | ISO 8601 timestamp of the acknowledgment |
Resolved variables
Available only in the resolved template category:
| Variable | Description |
|---|---|
{{ .ResolvedAt }} | ISO 8601 timestamp of the resolution |
Configure templates on a channel
- Navigate to Notification Channels and select the channel.
- Check Use Event-Specific Templates.
- Enter templates for each category you want to customize. Leave a category empty to inherit from defaults.
- Click Save.
- Click Test Channel to verify the output looks correct in your target service.
Template resolution priority
When Planekeeper renders a notification, it looks for a template in this order:
- Channel-specific template – configured directly on the channel
- Organization-level template – set in notification settings for the org
- Global default template – set by the system administrator
- Standard JSON payload – the built-in default when no template exists
The first non-empty template found is used. This lets you set sensible defaults at the organization level and override them for specific channels that need a different format.
Platform examples
Discord
Discord expects a content field for simple messages.
New Alert
```json
{
"content": "**{{ .Alert.Severity | upper }} Alert**: {{ .Alert.ConfigName }}\n\n**Artifact:** {{ .Alert.ArtifactName }}\n**Current:** {{ .Alert.DiscoveredVersion }} -> **Latest:** {{ .Alert.LatestVersion }}\n**Behind by:** {{ .Alert.BehindBy }} {{ .Alert.RuleType }}\n\n[Acknowledge]({{ .AcknowledgeURL }})"
}
```
Acknowledged
```json
{
"content": "{{ if .IsAcknowledged }}Acknowledged{{ else }}Unacknowledged{{ end }}: {{ .Alert.ConfigName }} - {{ .Alert.ArtifactName }}{{ if .IsAcknowledged }}\n\nAcknowledged by {{ .AcknowledgedBy }}{{ end }}"
}
```
Resolved
```json
{
"content": "Resolved: {{ .Alert.ConfigName }} - {{ .Alert.ArtifactName }}\n\nThe version has been updated and no longer triggers this alert."
}
```
Slack
Slack uses a text field for simple messages, or blocks for rich formatting.
Simple (text)
```json
{
"text": "*{{ .Alert.Severity | upper }}*: {{ .Alert.ConfigName }}\nArtifact: {{ .Alert.ArtifactName }}\nVersion {{ .Alert.DiscoveredVersion }} is {{ .Alert.BehindBy }} behind latest ({{ .Alert.LatestVersion }})\n<{{ .AcknowledgeURL }}|Click to Acknowledge>"
}
```
Rich (Block Kit)
```json
{
"blocks": [
{
"type": "header",
"text": {"type": "plain_text", "text": "{{ .Alert.Severity | upper }} Alert"}
},
{
"type": "section",
"fields": [
{"type": "mrkdwn", "text": "*Config:*\n{{ .Alert.ConfigName }}"},
{"type": "mrkdwn", "text": "*Artifact:*\n{{ .Alert.ArtifactName }}"},
{"type": "mrkdwn", "text": "*Current:*\n{{ .Alert.DiscoveredVersion }}"},
{"type": "mrkdwn", "text": "*Latest:*\n{{ .Alert.LatestVersion }}"}
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {"type": "plain_text", "text": "Acknowledge"},
"url": "{{ .AcknowledgeURL }}",
"style": "primary"
}
]
}
]
}
```
PagerDuty
PagerDuty uses the Events API v2 format. Set the routing_key to your PagerDuty integration key.
New Alert
```json
{
"routing_key": "YOUR_INTEGRATION_KEY",
"event_action": "trigger",
"dedup_key": "{{ .IdempotencyKey }}",
"payload": {
"summary": "{{ .Alert.Severity | upper }}: {{ .Alert.ConfigName }} - {{ .Alert.ArtifactName }} is {{ .Alert.BehindBy }} behind",
"source": "planekeeper",
"severity": "{{ .Alert.Severity }}",
"custom_details": {
"discovered_version": "{{ .Alert.DiscoveredVersion }}",
"latest_version": "{{ .Alert.LatestVersion }}",
"repository": "{{ .Alert.RepositoryURL }}",
"target_file": "{{ .Alert.TargetFile }}"
}
},
"links": [{"href": "{{ .AcknowledgeURL }}", "text": "Acknowledge in Planekeeper"}]
}
```
Resolved
```json
{
"routing_key": "YOUR_INTEGRATION_KEY",
"event_action": "resolve",
"dedup_key": "{{ .IdempotencyKey }}"
}
```
Best practices
Test before enabling. Always use the channel test feature to verify your template produces valid output for the target platform.
Include the acknowledge URL in new alert templates. This gives recipients a one-click path to acknowledge the alert without logging into Planekeeper.
Keep resolved templates short. The alert is no longer actionable, so a brief confirmation is sufficient.
Escape special characters. JSON strings require
\"for quotes and\nfor newlines within template strings.Start with platform defaults. Copy a platform example from this page and customize it rather than writing templates from scratch.
Use organization-level defaults for consistency. Set a base template at the organization level and only override at the channel level when a specific platform requires a different format.