AlphornAlphorn Docs

Webhook Templates

Shape incoming webhook payloads into structured Alphorn notifications with per-webhook template fields — no external proxies needed.

Many tools send webhooks in their own shape — GitHub, Sentry, Prometheus, and others each use different payload formats. Alphorn turns these into structured notifications without any external proxy or transform service.

You have three options, from most to least work on the sender side:

  1. Configure the sender natively — if the service supports custom webhook body templating (Grafana, Datadog, PagerDuty, Healthchecks.io, Gatus), make it send Alphorn's native format directly. See Integrations.
  2. Use Alphorn's per-webhook template fields — for services with fixed payload shapes (GitHub, Sentry, Prometheus Alertmanager), fill in the four template fields on the webhook and Alphorn extracts fields from the body.
  3. Rely on the heuristic fallback — leave the templates empty and Alphorn picks reasonable defaults from common field names.

This page covers options 2 and 3.

Template fields

Each webhook has four optional template fields in Webhooks → Edit:

FieldProducesNotes
Title templatetitle (≤ 200 chars)Short headline
Message templatemessage (≤ 4000 chars)Notification body
Tags templatetags[]Comma-separated; each part trimmed
Priority templatepriority (1–5)Must resolve to a number

Syntax

Templates use {dotted.path} placeholders resolved against the request body:

  • {title}body.title
  • {repository.full_name}body.repository.full_name
  • {data.issue.level}body.data.issue.level

Missing paths render as empty. There are no conditionals, loops, or helpers — just substitution. That's intentional: templates stay predictable and can't be used to smuggle logic or secrets.

Heuristic fallback

If a template is empty, Alphorn walks the body plus common wrapper keys (issue, pull_request, data, event, object, payload, form_response) and picks the first string-valued field from:

  • Title candidates: title, subject, name, headline
  • Message candidates: message, body, msg, text, content, description, summary

Top-level priority (number) and tags (array or comma-separated string) are read directly. If present, a top-level action string is prefixed to the title (opened: Some issue title).

If nothing matches, the message renders as the full JSON body.

GitHub

The event type is only carried in the X-GitHub-Event header, and templates resolve against the body only — so create one Alphorn webhook per GitHub event type and have GitHub deliver only that event to it.

Pull requests

Title:    PR {action}: {pull_request.title}
Message:  {sender.login} {action} PR #{pull_request.number} in {repository.full_name}
Tags:     github,pr,{action},{repository.name}
Priority: 3

Issues

Title:    Issue {action}: {issue.title}
Message:  {sender.login} {action} issue #{issue.number} in {repository.full_name}
Tags:     github,issue,{action},{repository.name}
Priority: 2

Pushes

Title:    Push to {repository.full_name}
Message:  {pusher.name} pushed to {ref} — {head_commit.message}
Tags:     github,push,{repository.name}
Priority: 2

Sentry

Sentry's default webhook plugin sends events under data.issue:

Title:    Sentry: {data.issue.title}
Message:  Culprit: {data.issue.culprit} — Level: {data.issue.level} — Events: {data.issue.count}
Tags:     sentry,{data.issue.level},{data.project_slug}
Priority: 4

PagerDuty (incoming)

Fan PagerDuty incidents out to other channels by pointing an extension at an Alphorn webhook:

Title:    PagerDuty: {event.data.title}
Message:  Service: {event.data.service.name} — Severity: {event.data.severity} — Status: {event.data.status}
Tags:     pagerduty,{event.data.severity},{event.data.status}
Priority: 5

Prometheus Alertmanager

Alertmanager sends arrays of alerts with commonLabels and commonAnnotations rollups. Use those instead of the per-alert array (templates can't index into arrays):

Title:    {commonLabels.alertname}
Message:  {commonAnnotations.summary}
Tags:     prometheus,{status},{commonLabels.severity}
Priority: 4

Tips

  • Priority can't branch on values. If you need different priorities per severity (critical = 5, warning = 3), either create separate webhooks — one per severity — or keep priority constant and route severities to different channels via filter rules.
  • Narrow event delivery at the source. Most services let you pick which events each webhook URL receives. Combining that with dedicated Alphorn webhooks keeps templates simple.
  • The full body is always stored as payload and available for filtering rules, so you don't have to cram everything into the templated message.

Using payload for routing

The full JSON body is stored on every message as payload, regardless of templating. Filter on it to route the same webhook to different channels:

ChannelFilterPurpose
PagerDutypayload.ref == "refs/heads/main"Only alert on pushes to main
Slack (#frontend)payload.repository.name == "frontend"Frontend repo activity
Slack (#backend)payload.repository.name == "api"Backend repo activity

See Filtering Rules → JSON Payload Paths for the full syntax.

Direct forwarding

For trivial cases, point any webhook at Alphorn without filling in any template. The raw body is accepted and the heuristic picks a reasonable title and message from common field names. Add templates later when you want more polished notifications.

On this page