Skip to content

Slack

Ongrid integrates with Slack in two distinct modes, configured on separate Settings panels:

ModeUse it forTokens
NotificationPush fired alerts into a channelJust the webhook URL
Socket Mode (IM)Talk to the agent from Slackxapp-… + xoxb-…

You can run both. They share nothing.

Notification (incoming webhook)

The simplest integration. Slack assigns a workspace-specific URL of the form https://hooks.slack.com/services/T…/B…/…; Ongrid POSTs the alert payload there.

What the payload looks like

The Slack sender renders one notify.Message into the attachments format (chosen over Block Kit because it carries the colored side rail that operators read as severity, and because the schema is JSON-flat and universally supported):

json
{
  "text": "[CRITICAL] node-01 swap_high",
  "attachments": [
    {
      "color": "#d92f2f",
      "fallback": "[CRITICAL] node-01 swap_high",
      "title": "node-01 swap_high",
      "text": "swap_in_pages > 1000 for 5m",
      "mrkdwn_in": ["text"],
      "fields": [
        {"title": "Severity", "value": "CRITICAL", "short": true},
        {"title": "Source",   "value": "alert",    "short": true},
        {"title": "Rule",     "value": "swap_high","short": true},
        {"title": "Incident", "value": "#1234",    "short": true},
        {"title": "Device",   "value": "#7",       "short": true},
        {"title": "Dedupe key", "value": "alert:swap_high:device=7", "short": false}
      ],
      "footer": "ongrid",
      "ts": 1717012345
    }
  ]
}

The text field stays populated so Slack push / email digest previews show a useful one-liner even when the client strips attachments.

Severity → color

SeverityHex
critical#d92f2f
warning#f2c037
info#36a64f
(unknown)#6f7a87

Pinned hex (not Slack's danger / warning sentinel) so the shade is stable across Slack client versions.

Setup

  1. In Slack: Apps → Incoming Webhooks → Add to Slack, pick the channel.
  2. Copy the webhook URL.
  3. In Ongrid: Settings → Channels → New → Provider = slack → paste the URL into Endpoint.

The Secret field on the form is ignored for Slack incoming webhooks. The URL is the credential; there is no separate signing surface. The channel builder drops the secret before constructing NewSlackSender.

Test the channel

The Test button on Settings → Channels sends a synthetic notify.Message{Severity: "info", Subject: "Ongrid test", …}. If the attachment shows up in the target channel with the green rail, the channel is wired.

Socket Mode (IM bridge)

Socket Mode is the two-way integration. Inbound user text is delivered over an outbound WebSocket (no public ingress required — same shape as Telegram getUpdates), and the manager replies using the standard Web API (chat.postMessage, chat.update).

Why Socket Mode and not Events API

Events API requires Slack to reach the manager inbound, which means a public HTTPS endpoint with a valid certificate. Most Ongrid deployments are private. Socket Mode is the supported alternative where the manager dials out — it honors HTTPS_PROXY / NO_PROXY and works behind NATs and the GFW. The validator rejects mode != stream for Slack:

slack only supports stream mode (Socket Mode)

Required tokens

Slack uses two tokens for Socket Mode. They have different scopes:

TokenPrefixUsed for
App-level tokenxapp-…apps.connections.open (gets a WebSocket URL).
Bot tokenxoxb-…chat.postMessage, chat.update, all other Web API calls.

Ongrid stores them as a JSON object inside im_apps.app_secret. The ParseSecret function validates the prefixes up front so a mis-pasted token surfaces as a clean error instead of a chat.postMessage 401.

json
{
  "app_token": "xapp-1-A0…",
  "bot_token": "xoxb-1234567890-1234567890-…"
}

Required app scopes

In the Slack app config:

OAuth & Permissions → Bot Token Scopes

  • app_mentions:read — receive app_mention events.
  • channels:history — receive message events in public channels.
  • groups:history — same for private channels.
  • im:history — same for DMs.
  • chat:writechat.postMessage / chat.update.

Socket Mode → Enable, then Basic Information → App-Level Tokens with scope connections:write to get the xapp-… token.

Event Subscriptions must be enabled (toggle on), with the bot subscribed to message.channels, message.groups, message.im, app_mention. Slack ships these over Socket Mode the same as it would over webhook.

allow_from

A comma-separated list of Slack user IDs that may converse with the bot. User IDs start with U (or W for Enterprise guests). At least one ID is required:

slack requires allow_from — at least one Slack user ID (e.g. UABC123, find via Profile → ⋯ → Copy member ID). Without it any workspace member could command a tool-equipped agent

Find your own ID in Slack: click your avatar → Profile → the menu → Copy member ID.

Non-allowlisted senders are silently dropped at handleEvent. The bot does not reply — it does not confirm it exists. The behavior mirrors Telegram allow_from.

Streaming reply behavior

When an allowlisted user sends a message:

  1. The bridge calls chat.postMessage once with a placeholder text (Working on it…) and records the returned ts.
  2. The agent runs. Each streaming chunk triggers a chat.update on the same (channel, ts).
  3. The terminal token flushes the final text into the same message.

Slack accepts a no-op chat.update (same text) silently — unlike Telegram, which returns a 400. There is no special swallow on the Slack side. See senderAdapter in stream.go.

Bot loops, edits, and thread IDs

  • Messages with bot_id != "" are dropped — they are the manager's own replies and would create a feedback loop.
  • Messages with any non-empty subtype (message_changed, channel_join, …) are dropped.
  • thread_ts is stored as ImThread.ImThreadID so a reply inside a thread continues the same conversation session.
  • Slack <@U…> mention markup is rewritten to a bare @U… token by stripMentions so the model sees a stable reference without a users.info round-trip per message.

Setup

  1. Create a Slack app: api.slack.com → Create New App → from scratch, pick the workspace.
  2. Enable Socket Mode, generate an app-level token with connections:write. Copy the xapp-… value.
  3. Set the Bot Token Scopes listed above.
  4. Install the app to the workspace. Copy the xoxb-… Bot User OAuth Token.
  5. Enable Event Subscriptions, subscribe the bot to the events listed above.
  6. In Ongrid: Settings → IM bridge → New → Provider = slack → Mode = stream → paste app_id (the bot's handle, e.g. ongrid-bot), the two-token JSON into App secret, and at least one Slack user ID into allow_from.

Reinstall the app after adding scopes

Slack does not retroactively grant scopes to already-installed bots. If chat.postMessage returns missing_scope after you added chat:write, reinstall the app to the workspace.

Keep-alive

The stream loop pings every 20s (pingInterval). Slack closes idle Socket Mode connections after ~30s. The ping runs in a goroutine so the read loop is never blocked behind a write. A Slack-initiated disconnect envelope closes cleanly and the supervisor reconnects immediately (no backoff sleep) with a fresh apps.connections.open URL.