Integration
Streamwake + Slack

Streamwake + Slack.
Incoming webhook + bot token — Block Kit cards the incident loop closes.

Pipe every Streamwake incident into the Slack channel your rotation already watches. The agent posts three Block Kit shapes today: an alert text card when a probe trips, an Approve/Reject actions block the moment an operator clicks Action, and a remediation-outcome context block once the loop closes. Pick the transport that matches the audit shape your rotation wants — incoming-webhook URL-only or OAuth-install Slack app with a bot token.

The incoming-webhook path is one form field on the Slack card — paste the URL, click Send test event, the Block Kit alert lands. The OAuth-install bot-token path adds three bot scopes on your Slack app (chat:write + channels:read + groups:read) so Streamwake can resolve any channel name to its id via conversations.open before posting the same Block Kit shapes via chat.postMessage. The end state is the same — a Block Kit card on the channel of your choosing.

How to connect

Five steps from a fresh Slack channel to a delivered Block Kit card.

The credential is either an incoming-webhook URL (Slack app feature-only, no OAuth scopes) or an xoxb-… bot token (OAuth-install Slack app, three required bot scopes). Whichever you pick, the form lives on the Slack card at /app/integrations and the test event lands from /api/v1/integrations/test.

  1. 1

    Open (or create) the Slack channel

    Open the Slack channel you want Streamwake alerts in — we recommend a fresh #streamwake channel so rotation chatter and incident cards stay separate.

  2. 2

    Pick the transport — incoming webhook vs OAuth-install bot

    Pick the transport that matches the audit shape your rotation wants. Incoming webhook is the URL-only path (Slack app needs only the Incoming Webhooks feature, no OAuth scopes). OAuth-install Slack app with a bot token is the channel-name-aware path (extra scopes: chat:write, channels:read, groups:read), which lets Streamwake resolve any channel name to a Slack id via conversations.open and post via chat.postMessage.

  3. 3

    Mint the credential

    Incoming webhook: in your Slack workspace, create a Slack app with the Incoming Webhooks feature turned on, generate a webhook URL for the channel from the feature page, copy it. OAuth-install: at api.slack.com/apps create a Slack app, add Bot Token Scopes (chat:write + channels:read + groups:read), Install to Workspace (OAuth v2 browser flow), then copy the xoxb-… Bot User OAuth Token. Either credential is the entire handshake — the URL for webhooks, the token + a channel name for bot-token transport.

  4. 4

    Paste it into the Streamwake Slack card

    Sign in to Streamwake at /app/integrations, find the Slack card, set Webhook URL (incoming-webhook transport) — or set Bot Token + Channel Name (OAuth-install transport) — set Default Severity, and click Save. The card SSRF-guards the webhook URL on save so a paste mistake between Save and a later Post is refused rather than silently forwarded.

  5. 5

    Send the test event

    Click Send test event on the Slack card. The card POSTs { channel: "slack" } to /api/v1/integrations/test, which posts the Block Kit test-event alert (severity-emoji + title + body) to the saved channel. Confirm the card lands; the Integration row stamps lastSlackDeliveryAt on a 2xx. From here, real incident cards — Approve/Reject prompts and remediation-outcome cards — flow through the same channel on the next loop close.

Block Kit payload schema

The three Block Kit shapes
Streamwake posts today.

Verbatim from sendSlack() in /api/v1/integrations/test (test-event alert), renderApprovalMessage() in business/slack/approval-message.tsx (Approve/Reject prompt), and renderRemediationMessage() in business/slack/remediation-message.tsx (recovery-verification outcome). Slack validates each block against Block Kit 1.x; the JSON below is what passes validation end to end on every transport.

Block Kit · alert text
posted by sendSlack() + the per-anomaly alert dispatcher
POST → webhook URL · Bearer <botToken>
{
  "text": "🟠 Streamwake agent anomaly: latency spike detected",
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "🟠 *Streamwake agent anomaly: latency spike detected*\nA monitored stream exceeded the p95 latency threshold for 3 consecutive probes."
      }
    }
  ]
}
Block Kit · approval card
posted by postApprovalToSlack() on operator-Action click
POST → webhook URL · Bearer <botToken>
{
  "text": "🚨 Approval requested — Encoder Failover Cascade",
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "Incident approval requested: Encoder Failover Cascade",
        "emoji": true
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Streamwake incident:* `inc_2k4dRm**`  ·  *PagerDuty incident:* `P3F9X**`"
      }
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": "*Ranked root cause*\nSecondary encoder dropped mid-segment, forcing A/B failover" },
        { "type": "mrkdwn", "text": "*Confidence*\n87%" },
        { "type": "mrkdwn", "text": "*Evidence*\nA/B timestamps crossed by 1.2s on the secondary encoder; bitrate ladder fell back one rung for 9 consecutive probes" },
        { "type": "mrkdwn", "text": "*Proposed remediation*\nRe-author the secondary encoder and pin to redundant ingest before reverting" }
      ]
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Operator note*\nSecondary encoder had been running on the same power rail for 41 minutes before failover"
      }
    },
    { "type": "divider" },
    {
      "type": "actions",
      "block_id": "streamwake_approval_inc_2k4dRm",
      "elements": [
        {
          "type": "button",
          "style": "primary",
          "text": { "type": "plain_text", "text": ":white_check_mark: Approve", "emoji": true },
          "action_id": "streamwake_approval_inc_2k4dRm",
          "value": "inc_2k4dRm"
        },
        {
          "type": "button",
          "style": "danger",
          "text": { "type": "plain_text", "text": ":x: Reject", "emoji": true },
          "action_id": "streamwake_rejection_inc_2k4dRm",
          "value": "inc_2k4dRm"
        }
      ]
    }
  ]
}
Block Kit · remediation outcome
posted by postRemediationToSlack() on incident resolve
POST → webhook URL · Bearer <botToken>
{
  "text": "✅ Remediated resolved — Encoder Failover Cascade",
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "Remediation outcome: Encoder Failover Cascade",
        "emoji": true
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*✅ Resolved* — `resolved`  ·  *PagerDuty incident:* `P3F9X**`"
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Streamwake incident:* `inc_2k4dRm**`"
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Operator note*\nSecondary encoder re-authorized; bitrate ladder returned to the original rung on the next probe"
      }
    },
    { "type": "divider" },
    {
      "type": "context",
      "elements": [
        { "type": "mrkdwn", "text": "Actor: *human*  ·  Transition: `2026-08-23T14:21:08.412Z`" }
      ]
    }
  ]
}
Field reference

A walk through the envelope, the Block Kit root, and the per-card body — drawn from the three renderers verbatim.

FieldSourceNotes
textenvelopePlain-text fallback — Slack clients that do not render blocks, and the dispatcher-side SSRF audit log, both read this string. Mirrors the block layout.
blocks[].typeblock typeOne of header / section / divider / actions / context. Slack Block Kit rejects any other top-level block type.
blocks[].textblock typeOn header and section blocks: type plain_text (header) or mrkdwn (section). plain_text forbids markdown; mrkdwn allows *bold*, `code`, and links.
blocks[].fields[]block typeSection block fields array — up to 10 items, each mrkdwn. Used on the Approve card for ranked root cause / confidence / evidence / proposed remediation.
blocks[].elements[].typeblock typeOn actions blocks: type button with text (plain_text), action_id, value, and optional style (primary | danger). On context blocks: type mrkdwn only.
blocks[].block_id / actions[].action_idper-alertBounded to 64 chars; carries a short id derived from the incident id so a future Slack interactivity callback can correlate without re-reading the message.
text (severity prefix)per-alertTest-event alert and per-anomaly alert prefix the text + the section mrkdwn body with the SLACK_SEVERITY_EMOJI for the row’s slackDefaultSeverity so the card carries the severity without a separate badge block.
Slack permissions

What the Slack app asks for
on each transport.

Two surfaces, two permission shapes. The incoming-webhook path is a Slack app feature — no OAuth, no scopes, the URL is the credential. The OAuth-install bot-token path adds three required Bot Token Scopes (chat:write + channels:read + groups:read) so Streamwake can resolve channel names to Slack ids and post via chat.postMessage. Three optional scopes unlock edge-case transports (public channels the bot isn’t in, DMs, multi-party DMs).

TransportScopeRequiredNotes
Incoming webhookIncoming Webhooks (feature)
required
No OAuth flow at all — the webhook URL is the entire credential. Workspace owner enables the feature on the Slack app, generates a URL for the channel, copies it.
OAuth-install bot tokenchat:write
required
Required for chat.postMessage, which the bot-token transport posts through. Without it the install refuses.
OAuth-install bot tokenchannels:read
required
Required for resolving a public-channel name to its Slack id via conversations.open. The resolver short-circuits when the input is already a Slack-shaped id (C123456).
OAuth-install bot tokengroups:read
required
Required for resolving a private-channel name (slack#group…) to its Slack id. Pair with channels:read so the resolver handles both shapes.
OAuth-install bot tokenchat:write.public
optional
Opt-in — needed only if the bot needs to post into a public channel the bot itself has not been invited to. Skip unless your routing table forces a cross-channel post.
OAuth-install bot tokenim:write
optional
Opt-in — needed only if Streamwake must post into a single-user DM (operator-direct escalation). Skip for channel-routed rotations.
OAuth-install bot tokenmpim:write
optional
Opt-in — needed only if Streamwake must post into a multi-party DM. Skip for the common channel-routed shape.
Recommended routing

One webhook per channel,
channel per scope.

The agent emits every incident as a typed severity + failure_mode pair (info / warning / critical + manifest_drift / cdn_brownout / per_title_encoder_regression / drm_cert_failure). One Slack channel per scope; one inbound webhook per channel so a compromised URL doesn’t take down the rest of the routing tree. Private rotation channels (encoder, DRM) flip to the OAuth-install bot-token transport so the channel can live in a private workspace. The table below is the channel + transport convention we recommend on day one.

ScopeSeverityChannelTransportConvention
critical / sev1
critical
#streamwake-critical
incoming webhook
One channel per page-on-call rotation; one webhook URL per channel. Severity is encoded into the SLACK_SEVERITY_EMOJI prefix on the alert text so on-call can triage without expanding the card body.
warning / sev2
warning
#streamwake-warn
incoming webhook
A single shared channel for everything below sev1; carries the per-incident alert text with the failure_mode prefix so the typed taxonomy reads off the lede.
info / digest-only
info
#streamwake-info
incoming webhook
Resolved incidents + daily/weekly digest fan-out lands here — same alert-text shape, but no on-call invocation. Enable the digest toggle on the Slack card to pick this channel up automatically.
manifest drift
warning
#streamwake-manifest
incoming webhook
Route manifest_drift incidents to a manifest-specific channel so the packager/encoder rotation can sweep the channel without internetwide noise. Use a fresh webhook URL on this channel so rotation-tracker scripts can give the URL its own TTL.
cdn_brownout
critical
#streamwake-cdn
incoming webhook
CDN-targeted channel — the alert text carries the per-PoP brownout title prefix (e.g. "cdn_brownout · IAD-PoP-3") so the CDN on-call can pivot to the right PoP before reading the body.
per_title_encoder_regression
warning
#streamwake-encoder
bot token
Encoder-rotation channel — bot-token transport here so the channel lives in the private encoder-only workspace. One channel name per encoder so a mis-rotated token only takes down one encoder, not the whole rotation. Streamwake resolves (botToken, channelName) via conversations.open on every post.
drm_cert_failure
critical
#streamwake-drm
bot token
DRM-only channel so the cert-window rotation owner is on it immediately. Bot-token transport keeps the channel private. Title prefix in the alert text is the cert name (e.g. "drm_cert_failure · Widevine-CA-2026-08").
Webhook rotation
Rotate an expired webhook in four steps
  1. 1.Open the affected Slack workspace’s app management → the Streamwake Slack app → Incoming Webhooks → generate a fresh webhook URL for the channel.
  2. 2.Copy it the moment Slack shows it — the Slack app page won’t reshow the URL after this dialog closes.
  3. 3. Sign in to Streamwake at /app/integrations, find the Slack card, paste the new URL into Webhook URL, and click Send test event. The card posts the same Block Kit alert text the test-event flow posts today.
  4. 4. Confirm the card lands; now delete the old webhook on the Slack app so a URL that leaked before the rotation can no longer reach the channel. Mouse-trap leak risk closes on the same rotation.
See the loop run

Run the Free Stream Check,
deliver the alert into your Slack channel.

Paste a playback URL into /stream-check — five checks (manifest, segments, bitrate ladder, CDN response, playback errors) in under a minute, no login. Pair it with the Book-a-demo block below for a guided walkthrough of the three Block Kit shapes + the per-severity Slack routing convention on your workspace.

Talk to engineering

Book a 20-minute walkthrough on your Slack workspace.

We’re happy to walk through the three Block Kit shapes, the per-severity Slack routing convention, and the webhook-rotation checklist on a real Slack workspace. Drop your details below and we’ll follow up within 1 business day.