Use the generic Callback notification channel to POST alert events as JSON to any custom Webhook URL — for IM bots, ITSM systems, in-house ops platforms, etc.
Overview
Callback is a generic HTTP Webhook notification channel that POSTs alert events from Nightingale to any URL as JSON. Beyond built-in channels (DingTalk, WeCom, Feishu, etc.), most custom integrations can be done via Callback: hooking into your internal ITSM/ticketing system, an in-house ops platform, a self-built bot, log pipelines such as Logstash/Loki, or forwarding events to downstream alerting platforms like Flashduty or PagerDuty.
- Use case: integrate with any HTTP endpoint and receive the full alert event JSON.
- Prerequisites: an HTTP/HTTPS URL reachable from Nightingale (filled per rule, switchable rule by rule).
- The whole setup has two steps: ① Keep the default callback channel (or create a new one) → ② Fill
callback_urlin the notification rule.
Unlike DingTalk, WeCom and similar channels, Callback does not render the message template by default — the request body is just
{{ jsonMarshal $events }}, the raw JSON array of events. If your downstream needs the template-rendered Markdown text, switch to variables like{{$tpl.content}}in the body — see “Customize the request body” below.
Step 1: Confirm or create a Callback notification channel
A built-in channel named Callback ships with Nightingale and usually works as-is. Create another one only if you need to separate Callbacks by business (e.g. one for ITSM, one for the in-house platform):
-
Sign in to Nightingale → left menu Notify → Notification Channels.
-
In the channel-type panel on the left, click Callback to enter the create page (URL
/notification-channels/add?ident=callback).
-
Most fields are pre-filled. Just change the “Name”:

Section Field Change? Notes Basic Name Yes e.g. Internal ITSM Callback. This is what you see when picking the channel in a rule.Basic Enable Keep on When off, this channel will not send. Variables Contact method Optional Callback rarely needs per-user routing; if you need to inject a phone/email into the body, pick the field here. Variables Parameter config Keep defaults, do not delete Pre-populated with callback_urlandnote; the actual target URL is filled per rule.HTTP URL Keep default {{$params.callback_url}}— replaced with the value filled in the rule.HTTP Method POSTDefault; can be PUT/GET.HTTP Headers Content-Type: application/jsonPre-filled. Add auth headers ( Authorization: Bearer ...) if needed.HTTP Timeout / Concurrency / Retry Usually keep defaults Default 10s timeout, 5 concurrency, 3 retries with 100ms interval. -
Scroll down to “Request body” — this is the heart of Callback:

The default request body is a single line:
{{ jsonMarshal $events }}Meaning: serialize all events in this batch into a JSON array and POST as the body. The downstream gets every field on the events (
id,rule_name,severity,tags,annotations,first_trigger_time,last_eval_time,is_recovered, …) and can parse them as needed. -
Click Save at the bottom-left — a “Callback” channel is created.
Note: Nightingale v8 no longer stores the target URL on the channel itself. One Callback channel can be reused by many notification rules, each rule filling its own
callback_url.
Step 2: Fill callback_url in the notification rule
-
Left menu Notify → Notification Rules → Add (or edit an existing rule).
-
In the “Notification config” section:

- Channel: pick the Callback channel you just created (or the built-in one).
- Message template: the default body does not use
$tpl, so any template works — leave blank or pickDefault. If you switched the body to use{{$tpl.title}}/{{$tpl.content}}, pick the matching template here. - Callback Url: the destination URL, e.g.
https://itsm.example.com/api/v1/n9e/webhook. Must start withhttp://orhttps://. - Note: optional comment, not part of the request.
- Severity / time window / labels: filter which events get notified.
-
After saving, click “Notification test” — the downstream endpoint should receive a JSON array.
Default $events JSON structure (excerpt)
[
{
"id": 12345,
"rule_id": 100,
"rule_name": "CPU usage too high",
"severity": 2,
"is_recovered": false,
"first_trigger_time": 1714377600,
"last_eval_time": 1714377900,
"trigger_value": "85.6",
"tags": ["ident=host01", "region=cn-bj"],
"tags_json": {"ident": "host01", "region": "cn-bj"},
"annotations": "{\"summary\":\"...\"}",
"target_ident": "host01",
"cluster": "default"
}
]
The full schema is
models.AlertCurEvent. If your downstream only needs a few fields, see the next section to trim the body.
Customize the request body
The default body is {{ jsonMarshal $events }}. You can replace it with any JSON / form layout and embed Nightingale’s built-in variables:
| Variable | Meaning |
|---|---|
$events |
The current batch of events as an array (use with jsonMarshal). |
$event |
The first event in $events (single-event scenarios). |
$tpl |
Template-rendered content, e.g. {{$tpl.title}}, {{$tpl.content}}. |
$params |
Custom parameters filled in the rule, e.g. {{$params.callback_url}}, {{$params.note}}. |
$sendto, $sendtos |
Target recipients (phone, email). Only injected when the channel is bound to a “contact method”. |
Example 1: Render Markdown to a Slack-compatible endpoint
{
"text": "{{$tpl.title}}\n{{$tpl.content}}"
}
Example 2: Forward as a Flashduty-style event
{
"event_status": "{{if $event.IsRecovered}}Ok{{else}}Critical{{end}}",
"alert_key": "{{$event.Hash}}",
"title_rule": "{{$event.RuleName}}",
"description": "{{$tpl.content}}"
}
Example 3: Bulk push to an in-house system
{{ jsonMarshal $events }}
The right body depends on what the downstream expects. If it expects a form, switch
Content-Typetoapplication/x-www-form-urlencodedand writekey1=val1&key2=val2.
FAQ
Q1: It looks like nothing was sent — how do I debug?
A: Open the alert event detail page in Nightingale and inspect the notification record — you can see the HTTP status code and response body for each callback. Common causes:
- URL missing the protocol (must start with
http:///https://); - The server requires auth — add an
Authorizationheader; - The server’s IP allowlist doesn’t include Nightingale’s egress IP;
- Request times out — raise
Timeoutin the HTTP config.
Q2: My downstream wants a single event, not an array?
A: The default body {{ jsonMarshal $events }} sends an array. To send a single event, change the body to {{ jsonMarshal $event }}, and disable batch sending (or lower the aggregation granularity) in the rule so each callback carries one event.
Q3: Can one Callback channel send to different URLs for different businesses?
A: Yes. No need to create new channels — just add another notification rule and fill a different callback_url.
References
- HTTP config explained — headers, query params, body, variables
- Message template guide — how to author
$tpl.title/$tpl.content