Skip to main content

Project Slack notifications (stacked on webhooks)

Date: 2026-07-22 Status: Approved design Stack: feat/project-pipeline-webhooks (#1345) โ†’ feat/project-slack-notifications

Goal

Make Slack the easiest notification destination: click Add to Slack, pick a channel on Slackโ€™s OAuth screen, choose events, done. Keep manual HTTPS endpoints. Surface this in the UI as Notifications (not โ€œWebhooksโ€).

Non-goals (v2)

  • In-app channel picker / changing channel without re-auth
  • Bot token + chat.postMessage multi-channel routing
  • Slack Marketplace listing / interactive Slack actions
  • Renaming API/DB from webhooks โ†’ notifications (UI only for now)
  • Org-level Slack install separate from per-project destinations

Product decisions

Data model

Extend project_webhooks (no new destination table):
  • provider text NOT NULL DEFAULT 'generic' CHECK (provider IN ('generic', 'slack'))
  • display_name text NULL โ€” e.g. #pipeline-alerts for Slack rows
Unchanged:
  • url โ€” for Slack, the hooks.slack.com/... URL from OAuth
  • secret โ€” still generated; not used when posting to Slack (URL is the credential). List/create API must not expose Slack URLs; show display_name instead
  • events[], enabled, delivery log / delivery_key behavior
Cap of 5 enabled destinations per project still applies to Slack rows.

OAuth + create flow

  1. User selects events in Project Settings โ†’ Notifications.
  2. Clicks Add to Slack.
  3. Frontend calls GET /projects/{project_id}/webhooks/slack/authorize?events=... (requires project:update).
  4. Backend returns Slack authorize URL with:
    • scope: incoming-webhook
    • signed, short-lived state binding project_id, events, user id, nonce
  5. User picks workspace + channel on Slack โ†’ Allow.
  6. Slack redirects to GET /api/integrations/slack/callback?code=&state= (or equivalent public callback route).
  7. Backend verifies state, exchanges code, reads incoming_webhook.{url, channel, channel_id}.
  8. Inserts project_webhooks row: provider=slack, url, display_name from channel, events from state.
  9. Redirects browser to Project Settings โ†’ Notifications with success toast. On deny/error, redirect with failure toast.
Platform env (Infisical): SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, plus a state-signing secret (dedicated or reused app secret). One Ionworks Slack App is installed into customer workspaces via this OAuth flow (distributed app, not per-customer app creation).

UI

Project Settings โ†’ Notifications
  • Short description: notify on selected project events via Slack or HTTPS
  • Event checkboxes (existing pipeline events)
  • Primary: Add to Slack
  • Secondary: URL field + Add HTTPS endpoint
  • Destinations list:
    • Slack rows: Slack ยท #channel (+ events, enabled toggle, menu)
    • HTTPS rows: host/path truncation (existing behavior)
  • No one-time HMAC secret dialog for Slack creates
  • Test action: Slack-formatted ping for Slack rows; existing test for generic
Backend routes/table names stay /webhooks and project_webhooks in v2 to avoid churn on #1345. Optional public rename later.

Delivery

Shared TaskType.WEBHOOK worker: Status accents: completed green, failed red, canceled gray; test ping neutral. Retries: unchanged (5xx / network raise; 4xx permanent fail). Map Slack errors like channel_not_found / no_service to permanent failure. Idempotency: same delivery_key scheme.

Edge cases

  • User denies Slack โ†’ toast, no row
  • App install blocked by workspace admin โ†’ surface Slack error + help
  • Re-adding the same channel creates another row (no upsert by channel_id in v2)
  • Change channel: delete destination + Add to Slack again
  • Enabled-cap race: existing advisory lock covers Slack inserts that enable immediately

Testing

  • Unit: OAuth state sign/verify; Block Kit builder; delivery branch (Slack vs generic)
  • Callback handler with mocked Slack token exchange โ†’ creates provider=slack row with expected fields
  • No live Slack workspace in CI

Implementation stack

Suggested implementation slices (for the plan, not separate stacks unless the PR grows):
  1. Migration + provider field + API list/create shapes
  2. Slack OAuth authorize + callback + row create
  3. Delivery Block Kit branch + host allowlist
  4. Frontend Notifications rename + Add to Slack button