Skip to content
RouteSpear
Documentation
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
Email 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.

Email

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

Screenshot