Slack
Ongrid integrates with Slack in two distinct modes, configured on separate Settings panels:
| Mode | Use it for | Tokens |
|---|---|---|
| Notification | Push fired alerts into a channel | Just the webhook URL |
| Socket Mode (IM) | Talk to the agent from Slack | xapp-… + 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):
{
"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
| Severity | Hex |
|---|---|
| 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
- In Slack: Apps → Incoming Webhooks → Add to Slack, pick the channel.
- Copy the webhook URL.
- 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:
| Token | Prefix | Used for |
|---|---|---|
| App-level token | xapp-… | apps.connections.open (gets a WebSocket URL). |
| Bot token | xoxb-… | 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.
{
"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— receiveapp_mentionevents.channels:history— receivemessageevents in public channels.groups:history— same for private channels.im:history— same for DMs.chat:write—chat.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:
- The bridge calls
chat.postMessageonce with a placeholder text (Working on it…) and records the returnedts. - The agent runs. Each streaming chunk triggers a
chat.updateon the same(channel, ts). - 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_tsis stored asImThread.ImThreadIDso a reply inside a thread continues the same conversation session.- Slack
<@U…>mention markup is rewritten to a bare@U…token bystripMentionsso the model sees a stable reference without ausers.inforound-trip per message.
Setup
- Create a Slack app: api.slack.com → Create New App → from scratch, pick the workspace.
- Enable Socket Mode, generate an app-level token with
connections:write. Copy thexapp-…value. - Set the Bot Token Scopes listed above.
- Install the app to the workspace. Copy the
xoxb-…Bot User OAuth Token. - Enable Event Subscriptions, subscribe the bot to the events listed above.
- 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.