Skip to main content

Callback (generic webhook)

POST events to any HTTP endpoint: the URL, headers and body template, the variables they can use, and when a call counts as delivered.

Where this page ends: every event a notification rule matches is POSTed to an HTTP endpoint you own — a ticketing system, an internal bot, an automation service — in a body shaped the way that endpoint expects.

The open-source edition ships a Callback media type, already enabled. In most cases you do not create a new one; you point a notification rule at it and give it a URL.

The fastest path: reuse the built-in media type​

The built-in Callback is deliberately generic: its URL is {{$params.callback_url}}, so the actual address is supplied per notification rule, not on the media type.

  1. Open a notification rule (Alerts & Notifications → Notification rules) and add a notification config.
  2. Pick Callback as the media type. Two fields appear: Callback Url and Note.
  3. Put the full endpoint address into Callback Url. Note is free text, available to the body template as $params.note.
  4. Click Run test at the top right of that notification config, then save.

One media type, any number of endpoints: each rule (and each notification config within a rule) carries its own Callback Url.

What the endpoint receives by default:

ItemDefault
MethodPOST
HeaderContent-Type: application/json
Body{{ jsonMarshal $events }} — a JSON array of event objects, even when there is only one

The event object is the same structure the history table stores; its fields are listed in Notification variables.

When you need your own: the fields on the media type​

Create a separate media type (Alerts & Notifications → Media types → Add → Callback) when the target wants a fixed address, an auth header, or a body that is not the raw event array.

FieldNotes
URLCan contain template variables, e.g. {{$params.callback_url}} or https://hooks.example.com/{{$params.path}}
MethodGET / POST / PUT
HeaderKey/value pairs; values may contain variables
ParamsQuery parameters appended to the URL; values may contain variables
BodyA Go template; see below
TimeoutMilliseconds, 10000 by default
ConcurrencyParallel requests for this media type, 5 by default
Retry times / Retry interval3 times, 100 ms apart, by default
Skip TLS verifyFor endpoints with a self-signed certificate. There is no custom-CA option
ProxyWhen the endpoint is only reachable through a proxy

Above those, a Variable configuration block holds two more things:

  • Contact — which contact field to read from the users and teams picked in the rule; see Contact methods. Leave it empty for a plain webhook.
  • Custom parameters — fields that appear in the notification rule when this media type is picked. Read them in templates as $params.<key>; the built-in media type's Callback Url and Note are exactly this.

What the templates can see​

URL, header values, query parameter values and the body are all rendered with the same variables:

VariableWhat it holds
$eventsAll events in this send, as an array
$eventThe first event — convenient when you know each send carries one
$paramsThe custom parameters filled in on the notification rule, e.g. $params.callback_url
$tplThe message template picked on the rule, already rendered; each template field is a key, e.g. $tpl.content
$sendtoOne recipient's address (see the next section)
$sendtosAll recipients' addresses, as an array
{{.name}}A site variable — the place for tokens and secrets

Template functions such as jsonMarshal are available everywhere.

Build JSON with jsonMarshal, not quotes​

The body is rendered by Go's html/template. Strings you print directly are HTML-escaped: a rule called disk "full" & <slow> arrives as disk &#34;full&#34; &amp; &lt;slow&gt;. Wrapping a value in jsonMarshal emits it as a correctly escaped JSON literal instead, quotes included:

{
"title": {{ jsonMarshal $event.RuleName }},
"severity": {{ $event.Severity }},
"status": {{ if $event.IsRecovered }}"resolved"{{ else }}"firing"{{ end }},
"labels": {{ jsonMarshal $event.TagsMap }},
"note": {{ jsonMarshal $params.note }}
}

Site variables ({{.name}}) are the exception: they are inserted verbatim, so a + in a token is not rewritten.

Recipients: one request, or one per person​

If the media type has a Contact set and the rule picks users or teams, Nightingale resolves each person's address for that key. People who have not filled it in are skipped silently. Then:

  • If $sendtos appears anywhere in the media type's configuration, one request goes out carrying every address;
  • otherwise one request per recipient, each with its own $sendto.

With no Contact set and no recipients — the usual case for a plain webhook — one request goes out per send.

When a call counts as delivered​

  • Only HTTP 200 is success. 201, 202 and 204 are recorded as failures, with the status code and response body. If you own the endpoint, return 200.
  • A response that is not 200 is not retried. Retries only cover failing to connect at all (DNS, refused, timeout), up to the retry count, waiting the retry interval in between.
  • Callback sends go through a queue with the media type's concurrency. If the queue is full, the send is recorded as failed with queue is full.

The record — target, status code, response body — is in the event's notification records; see Retries and delivery status.

Common errors​

SymptomCause
callback provider requires URLThe URL field is empty. With the built-in media type, the rule's Callback Url was left blank
Body arrives with &#34; in itA string was printed directly; wrap it in jsonMarshal
Status 204, recorded as failedOnly 200 is success; change the endpoint's response code
Every recipient gets a separate request$sendtos is not used anywhere; reference it in the body if the endpoint wants a list
failed to parse template in the recordA template syntax error in the URL, a header or a parameter

Next​