Planekeeper is currently in alpha development. Features and APIs may change. Feedback is welcome! Request early access to get started.

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

FunctionDescriptionExample
upperConvert to uppercase{{ .Alert.Severity | upper }}
lowerConvert to lowercase{{ .Event | lower }}
jsonJSON-encode a value{{ .Alert | json }}

Template categories

Templates are organized into three categories, each covering specific event types:

CategoryEvents coveredUse for
New alertalert.created, alert.escalatedInitial notifications and severity escalations
Acknowledgedalert.acknowledged, alert.unacknowledgedAcknowledgment state changes
Resolvedalert.resolvedAlert resolution notifications

Available variables

Common variables (all categories)

These variables are available in every template:

VariableDescription
{{ .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:

VariableDescription
{{ .AcknowledgeURL }}One-click acknowledgment callback URL
{{ .PreviousSeverity }}Previous severity before escalation (escalated events only)

Acknowledged variables

Available only in the acknowledged template category:

VariableDescription
{{ .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:

VariableDescription
{{ .ResolvedAt }}ISO 8601 timestamp of the resolution

Configure templates on a channel

  1. Navigate to Notification Channels and select the channel.
  2. Check Use Event-Specific Templates.
  3. Enter templates for each category you want to customize. Leave a category empty to inherit from defaults.
  4. Click Save.
  5. 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:

  1. Channel-specific template – configured directly on the channel
  2. Organization-level template – set in notification settings for the org
  3. Global default template – set by the system administrator
  4. 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

  1. Test before enabling. Always use the channel test feature to verify your template produces valid output for the target platform.

  2. Include the acknowledge URL in new alert templates. This gives recipients a one-click path to acknowledge the alert without logging into Planekeeper.

  3. Keep resolved templates short. The alert is no longer actionable, so a brief confirmation is sufficient.

  4. Escape special characters. JSON strings require \" for quotes and \n for newlines within template strings.

  5. Start with platform defaults. Copy a platform example from this page and customize it rather than writing templates from scratch.

  6. 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.