Skip to main content
Version: v5.7.0

Notification connector

Included from Basic tier

An instance-level channel connector: the admin-configured egress a user's or team's connector-backed notification route rides. Users pick routes; only platform administrators configure where platform data may go — a webhook route can only target URLs inside a connector's allowlist, and a slack/teams route delivers to the connector's own stored URL, never an arbitrary one. The surface is NotificationAdminService (API v2, PostgreSQL-backed), gated on the /admin updatable determination except ListNotificationConnectorOptions, the id/name/kind picker any authenticated caller reads.

Kinds​

  • webhook — the generic HMAC-signed egress: admin-allowlisted HTTPS targets, per-route target URL and payload template.
  • slack / teams (never an entitlement) — native chat channels. The connector stores the service's incoming-webhook URL, which is a capability secret: possession of the URL is the authenticator, so it is held in the same encrypted write-only slot as the webhook kind's signing secret (webhook_url on create/update, webhook_url_set in responses) and is never echoed by any response, audit row, error or log. HMAC signing, the URL allowlist, default headers and per-route targets/templates do not apply to these kinds — the payload shape is implied by the kind. Validation is per kind: a slack URL must carry a Slack incoming-webhook host (hooks.slack.com, or hooks.slack-gov.com for GovSlack); a teams URL must be a Teams Workflows webhook (the modern Power Automate lane) — the retired Office 365 connector hosts (*.webhook.office.com, outlook.office.com) are refused, since Microsoft has retired that lane.

Fields​

FieldTypeMeaning
kindstringwebhook, slack or teams. Fixed at create.
namestringUnique instance-wide.
url_allowliststring[]Webhook kind only. The HTTPS URL prefixes deliveries may target. Each entry is a bare https:// prefix (host required; no credentials, query or fragment). A target must match an entry's host (and port) exactly and extend its path on a segment boundary.
default_headers_jsonstringWebhook kind only. A JSON object of non-secret header values added to every delivery (e.g. a routing key). The reserved delivery headers (Content-Type, X-OpsChain-*) cannot be overridden. Secrets do not belong here — the signing secret is the authenticator.
signing_secret_setboolWebhook kind: whether a signing secret is stored. The secret itself is write-only: supplied on create (required) or update (rotation), stored encrypted with the server's encryption keys, never echoed by any response, audit row or log. A keyless server refuses to store one (typed ERROR_REASON_SERVER_NOT_CONFIGURED).
webhook_url_setboolSlack/teams kinds: whether an incoming-webhook URL is stored. The URL itself is write-only with exactly the signing secret's storage posture (encrypted, rotation via update, keyless server refused).
enabledboolA disabled connector leaves the options picker, stops matching at dispatch and dead-letters queued deliveries.

Bounds: 50 connectors per instance; name ≤ 255 characters. A connector any notification route still references refuses deletion.

Delivery security posture​

  • Admin allowlist — webhook targets (route config and the test verb alike) must fall inside the connector's url_allowlist; HTTPS only. A slack/teams delivery has exactly one possible target — the connector's stored URL — so no allowlist exists for those kinds.
  • SSRF hygiene — a DNS name resolving to a loopback, link-local (the cloud metadata range included), unspecified or multicast address is refused; a literal IP (or localhost) allowlist entry is the explicit admin opt-in that permits such an address. The connection dials the vetted address, so a rebinding resolver cannot swap the target between check and dial. Redirects are never followed; the delivery times out at 10s.
  • HMAC signing — every webhook payload carries X-OpsChain-Timestamp and X-OpsChain-Signature: v1=hex(HMAC-SHA256(secret, "<timestamp>.<body>")) so receivers authenticate the sender and reject replays by timestamp. Unsigned webhook deliveries are never sent. HMAC does not apply to the slack/teams kinds — neither service verifies a sender signature, and the capability URL is the authenticator — so a chat delivery carries no X-OpsChain-* headers at all.
  • Allowlisted content — payloads render from the notification row's allowlisted field set only (event type, classification, summary, node path, thread key, deep link) — never event payloads, resolved secrets or PII.
  • Retry / dead-letter — transient failures retry with backoff (30s/2m/8m/30m, 5 attempts) before the row dead-letters as delivery_status: failed with the reason in delivery_error — an HTTP refusal records the status plus the leading printable slice of the target's answer (e.g. Slack's target answered HTTP 400: invalid_payload), so a shape mismatch is legible from the delivery evidence; permanent refusals (target outside the allowlist, disabled or removed connector, a connector whose kind no longer matches the route, keyless server) dead-letter immediately.

Payload shapes​

A slack connector posts a Slack Block Kit document: a plain-text fallback, a header naming the event, an mrkdwn section with the severity (and state) and the rendered summary, the node path as context, and the deep link (linked when absolute, as context text when it is a bare GUI path). A teams connector posts a Teams Workflows message envelope carrying one Adaptive Card (schema 1.4): title, summary text, a severity/ node FactSet and an Action.OpenUrl back to the GUI for an absolute deep link. Both render from the same allowlisted field set as the webhook templates.

A webhook route picks one of three shapes (template in the route config; the test verb accepts the same):

  • generic (default) — {"version":"1","id":…,"event_type":…,"severity":…, "state":…,"object_kind":…,"summary":…,"node_path":…,"thread_key":…, "deep_link":…,"created_at":…,"test":…}.
  • slack — a Slack-compatible incoming-webhook document: text fallback plus one mrkdwn section block.
  • teams — a Teams-compatible MessageCard (summary, title, text, severity theme colour, node/link facts).

The webhook kind's Slack/Teams-compatible templates remain for customers fronting Slack, Teams, PagerDuty or ServiceNow behind their own signed webhook endpoints; the native slack/teams kinds are the simpler lane when the admin can paste the service's incoming-webhook URL directly. Richer native behaviour (threading, update-in-place) is planned for a later release.

Point a Slack/Teams incoming-webhook URL at its own kind, not at a generic webhook connector. Slack and Teams only accept their own body shapes, so a webhook connector aimed at an incoming-webhook URL fails on every delivery whose route keeps the default generic template — Slack answers HTTP 400: invalid_payload, now captured verbatim in the row's delivery_error. The trap's signature is a green Test with a silent channel: the test verb's template is chosen in the test dialog independently of any route's stored template, so a test sent with the slack template proves the connector reaches Slack while routes configured with generic keep failing. The native kinds close the trap — they carry no template, so their test verb posts exactly the shape real deliveries use (the same renderer end to end): a green slack/teams test proves the lane.

The test verb takes no arguments for a slack/teams connector — it posts the fixed test payload, in the kind's own shape, to the stored webhook URL.

Switching an existing connector's kind is a supported update: supply kind together with the target kind's secret material (webhook_url for slack/teams; signing_secret + url_allowlist for webhook) — e.g. re-pointing a webhook connector that already fronts a Slack URL at the native slack kind by pasting the same URL as webhook_url. The switch clears the old kind's fields; routes still bound to the connector under the old channel kind stop matching (queued rows dead-letter with connector kind no longer matches the route) and are re-created on the new kind.

Mutations record api:notification_connectors:create|update|destroy and api:notification_connectors:test audit events (names, ids, the test target and outcome — never headers or secrets, and never a slack/teams connector's stored URL, which is a capability secret).