Documentation menu
Channels
The eight destinations a notification can reach, what each needs, what triggers a send, and what happens when one keeps failing.
A channel is a configured destination: one Slack webhook, one PagerDuty service, one email list. A policy decides which incidents reach which channel; the channel itself only knows how to deliver. Add one from Alerts → Channels → Add channel. Channels and policies open on dedicated pages for adding and editing, with space for configuration and payload previews. Navigating away with unsaved changes asks whether to discard them. If you choose New policy before creating a channel, the page explains the next step and offers Create a channel. After saving the channel, the policy page opens with that destination selected.
Credentials are encrypted at rest with the deployment’s secrets keyring and
are never shown again after saving, only a hint (the last few characters)
and whether one is stored. If the portal has no keyring configured
(ROUTESPEAR_SECRETS_KEYS unset on the portal API and on routespear-alert),
channel kinds that need a credential cannot be saved, and the list says so.
The eight kinds
| Kind | What it needs | Sends on |
|---|---|---|
| Webhook | URL, Method (POST or PUT); optional Signing key | open, escalate, ack, resolve, test |
| Slack | Incoming webhook URL | open, escalate, ack, resolve, test |
| Microsoft Teams | Webhook URL (incoming webhook or Workflows) | open, escalate, ack, resolve, test |
| PagerDuty | Region (us or eu), Integration (routing) key | open/escalate → trigger; ack → acknowledge; resolve → resolve; test |
| ServiceNow | Instance URL, optional Table/Assignment group/Caller; Username+Password or an OAuth bearer token | creates on open, updates the same record on escalate/resolve |
| SMS (Twilio) | Recipients (E.164), From number; Account SID, Auth token | open, escalate, ack, resolve, test |
| Recipients; Send via platform relay or your own SMTP (SMTP host/port/username/password, Implicit TLS) | open, escalate, ack, resolve, test |
Every notification carries the incident’s summary, severity, dimensions,
detail (value versus threshold, the agents that measured it and any excluded
for fidelity), and a link back to the incident and its rule, plus the
rule’s runbook URL if it has one. The title is prefixed [CRITICAL],
[RESOLVED], [ACKED], [SEVERITY ↑] or [TEST] depending on what
triggered it.
Webhook
POSTs the incident as JSON. Set a Signing key and the request carries
X-RouteSpear-Signature: t=<unix>,v1=<hex hmac-sha256(t + "." + body)>, so a
receiver can verify origin and reject old replays. X-RouteSpear-Event and
X-RouteSpear-Delivery are always sent.
Destinations resolving to a private, loopback, or link-local address are refused by default, so a policy cannot be pointed at your own internal network by mistake, self-hosted deployments that need that can lift the restriction, but it is off by default for a reason.
Custom payloads
Turn on Custom payload to define the JSON your destination expects. The editor offers field completion, inferred callback types, hover descriptions, and a preview rendered by the same evaluator used for delivery. Choose an example event (open, escalate, remind, ack, resolve, or test) to check your expression. Preview uses synthetic data and never contacts the destination.
Send example payload sends the displayed example to the URL in the form,
using the current method, headers, payload expression, and credentials. You can
test an unsaved channel or draft changes without saving. The result shows the
receiver’s HTTP status or the delivery error. Each click sends once, without
automatic retries; the request includes X-RouteSpear-Test: true and a fresh
signature timestamp. Existing stored credentials are used unless you choose
Replace. These draft tests do not create incidents or delivery-history rows.
Write one expression returning an object or array. RouteSpear handles JSON encoding and signs the final rendered body. For example:
{
incident_id: incident.id,
title: rule.name,
severity: incident.severity,
message: incident.summary,
url: links.incident,
timeline: incident.timeline.map((event) => ({
timestamp: event.at,
message: event.summary
}))
}
alert contains the full notification. incident, rule, detail, links,
tenant, and event are convenient aliases. incident.timeline includes
recorded opening, acknowledgement, and resolution milestones in time order;
it does not include severity history, collaboration messages, or measurement
samples. detail holds rule-specific evidence, so inspect its actual shape
before depending on a field. Missing object fields resolve to null; use
?? to provide a default.
Use array map, filter, reduce (with an initial value), join, slice,
and includes; Object.keys, Object.values, and Object.entries enumerate
keys in sorted order. Strings support toUpperCase, toLowerCase, trim,
and includes. Callback parameters must be names. A callback can return an
expression directly or use a block containing one return. Objects support
spread; template strings, arithmetic, comparisons, conditional expressions,
and boolean/default operators are available.
This is a bounded data-expression language with JavaScript syntax. It does not support loops, assignments, arbitrary functions, optional chaining, imports, asynchronous work, or network/filesystem access. Limits are 16 KiB of source, 10,000 evaluation steps, a 4 MiB intermediate allocation budget, and 256 KiB of output with at most 64 levels. Rendering failures stop the delivery and appear in its error; RouteSpear does not substitute another body.
Ask Spear can derive an expression from a description or an example of the JSON your receiver expects. It uses the current unsaved payload and the server’s expression reference. Copy its suggestion into the editor, preview the relevant events, and review before saving. Channel credentials and the destination URL are not included in this editor’s assistant context.
Turn off Custom payload to keep the standard notification body. An
optional Bearer token authenticates the request separately from the payload.
The standard body includes incident.id; the starter custom expression includes
it as incident_id so receivers can correlate events for the same incident.
Slack
One message per event, colour-coded by severity, through a Slack incoming webhook. Each event is its own message rather than an update to a thread, so an incident’s open, escalate and resolve appear as separate lines in the channel.
Microsoft Teams
An Adaptive Card per event, through an incoming webhook or a Workflows URL.
PagerDuty
Uses the Events API v2 with the incident’s own id as the dedup_key, so an
escalation re-triggers the same PagerDuty alert rather than opening a second
one, and resolving here resolves it there. Acknowledging in PagerDuty is not
reflected back into RouteSpear yet, acknowledge from the incident page to
stop escalation on this side.
ServiceNow
Creates a record on the configured table (incident by default) when an
incident opens, then patches the same record by sys_id on escalation and
sets it closed on resolve. Authenticate with a username and password, or an
OAuth bearer token, not both.
SMS
One message per recipient, trimmed to fit, title, severity and a short link. Reserve it for the incidents that genuinely need a phone to buzz; escalation steps and severity minimums on a policy are how you keep it to that.
Sends HTML and plain text. Send via the platform’s relay (nothing to configure, where one is set up for this deployment) or your own SMTP server, with STARTTLS or implicit TLS.
Portal visibility
Incidents, health history and rule problems always appear in the portal. No channel or policy is required. Add external channels only when someone should also receive a notification outside the portal.
Testing a channel
Send test on a channel queues a test notification and polls its
delivery, updating in place: Sending… while queued, then either Test
delivered to {name} (with the provider’s response code) or Test to
{name} failed/was skipped, with the error if there is one. A test carries
no incident, so it exercises the same code path a real notification would
without needing something to actually be broken first.
When a channel keeps failing
Each failed delivery increments the channel’s failure count, shown as N failing next to it in the list. After 20 consecutive failures the channel disables itself automatically, with the reason recorded and visible on the channel, a receiver that has been rejecting everything for hours stops being retried into forever, and a policy that routes to a disabled channel simply has nothing sent there until someone re-enables it.
A single failed delivery retries with backoff (1 minute, 5, 15, then 1 hour and 4 hours) before it is given up on. A permanent failure (a 4xx response other than 408 or 429, or a destination invalid at send time) is not retried at all.
Editing and deleting
Editing an existing channel keeps its kind fixed; only Add channel chooses one. Credentials are write-only, leave the field blank on an edit to keep what is stored, or switch on Replace to overwrite it. Deleting a channel is refused while a policy still routes to it; deliveries already sent stay in incident history either way.
Next
- Policies: deciding which incidents reach which channel
- Health rules: what generates the incidents in the first place