Шаблоны интеграции Slack для оповещений и рабочих процессов

Slack — это слой интерфейса пользовательского взаимодействия для рабочих процессов и доставки уведомлений.

Содержимое страницы

Интеграции со Slack могут показаться обманчиво простыми, потому что вы можете отправить сообщение одним HTTP-запросом. Интересная часть начинается тогда, когда вы хотите, чтобы Slack был интерактивным и надежным.

Уведомления интеграции Slack

В этом подробном обзоре Slack рассматривается как три различных поверхности интеграции:

  • Приемник уведомлений для односторонних предупреждений через входящие веб-хуки.
  • Движок рабочих процессов через Workflow Builder и пользовательские шаги рабочих процессов.
  • Интерфейс событий через кнопки Block Kit, слэш-команды и полезные нагрузки действий.

Эта страница описывает, как системы пересекают границу в общий интерфейс пользователя, который также может генерировать события обратно в вашу архитектуру, а не о философии оповещений. О стратегии и маршрутизации оповещений см. Проектирование современных систем оповещения для команд наблюдаемости.

Сопутствующие материалы:

Каноническая рамка и размещение в паттернах интеграции

Slack — это не просто место, где оповещения умирают. При правильном использовании Slack становится системным интерфейсом, где сообщения являются артефактами состояния, а взаимодействия пользователей — событиями.

Эта страница канонически размещена под /app-architecture/integration-patterns/slack/, потому что основной вопрос заключается не в «стоит ли оповещать», а в «какой контракт между нашей системой и Slack».

Если ваше решение требует любого из следующего, вы находитесь в области паттернов интеграции, а не простого оповещения:

  • Цикл принятия решений, где действие блокируется до утверждения человеком.
  • Рабочий процесс, где Slack собирает контекст и запускает шаги.
  • Цикл событий, где Slack генерирует действия, на которые ваша система подписывается.

Платформа Slack намеренно поддерживает как одностороннюю отправку сообщений, так и двустороннее взаимодействие через URL запросов и полезные нагрузки взаимодействий. Входящие веб-хуки являются основным способом отправки JSON-полезных нагрузок, включая строительные блоки Block Kit, в канал (Отправка сообщений с использованием входящих веб-хуков). Интерактивность возвращается вашему приложению в виде HTTP POST-запросов к настроенному URL запроса, и эти полезные нагрузки кодируются в формате формы с полем JSON (Обработка взаимодействия пользователя).

Slack как приемник уведомлений

Входящие веб-хуки — это самый быстрый путь к ценности для оповещений и обновлений статуса. Веб-хук — это уникальный URL, привязанный к установке приложения, и вы отправляете к нему JSON-сообщение (Отправка сообщений с использованием входящих веб-хуков).

Субъективное мнение: веб-хуки — отличный выбор по умолчанию, когда вам нужны сообщения «отправить и забыть», и вам не нужен Slack как поверхность управления. Веб-хуки также являются отличным способом разделить настраивание и вашу конечную архитектуру приложения.

Slack как движок рабочих процессов

Workflow Builder существует потому, что чат — это там, где работа действительно происходит. Рабочие процессы могут быть простыми или сложными и могут подключаться к приложениям (Руководство по Workflow Builder).

Пользовательские шаги рабочих процессов позволяют представить ваши системы как переиспользуемые строительные блоки внутри Workflow Builder (Шаги рабочих процессов). Это другая форма интеграции, чем боты в каналах. Это приближает вашу интеграцию к «инструментам внутри Slack», а не к «сообщениям извне».

Субъективное мнение: если ваша организация уже мыслит категориями рабочих процессов и согласований, шаги рабочих процессов могут казаться более нативными, чем специализированные боты.

Slack как интерфейс событий

Block Kit превращает сообщение в поверхность интерфейса пользователя (Block Kit). Интерактивные компоненты, такие как кнопки, генерируют полезные нагрузки действий, обычно полезные нагрузки block_actions, которые отправляются в ваше приложение, когда пользователь нажимает (полезная нагрузка block_actions).

Кнопки имеют явные идентификаторы action_id и необязательное значение, и должны находиться внутри блоков section или actions (Элемент кнопки). Когда вы проектируете сообщение с кнопкой, вы проектируете источник событий.

Здесь темы, подобные проверке запросов, необходимым правам доступа и безопасным внутренним триггерам, становятся центром проектирования.

Архитектурные паттерны, которые масштабируются

Поток веб-хука для односторонних оповещений

[сервис] -> [форматировщик оповещений] -> [входящий веб-хук Slack] -> [канал]

Входящие веб-хуки принимают JSON-полезные нагрузки и поддерживают макеты Block Kit (Отправка сообщений с использованием входящих веб-хуков).

Когда в FAQ спрашивают о самом быстром способе отправки оповещений, обычно это именно оно.

Поток через брокер с очередью для надежности и обратного давления

[сервисы] -> [тема очереди] -> [диспетчер Slack] -> [API Slack]
                     |                 |
                     |                 +-> [обработчик ограничения частоты]
                     +-> [очередь мертвых писем]

Ограничения частоты запросов Slack применяются к HTTP-базовым API, включая входящие веб-хуки, и Slack возвращает HTTP 429 с заголовком Retry-After, когда вы превышаете лимиты (Ограничения частоты).

Субъективное мнение: если вы отправляете оповещения напрямую от каждого сервиса, первый инцидент превращается в DDoS-атаку против вашей собственной интеграции со Slack. Диспетчер за очередью, как правило, создает более спокойную архитектуру.

Паттерн автоматизации рабочих процессов с согласованиями

[оповещение] -> [сообщение Slack с кнопкой] -> [нажатие кнопки]
   -> [полезная нагрузка действия] -> [обработчик согласования] -> [внутренний API] -> [обновление сообщения]

Интерактивность Slack требует настройки URL запроса и включения интерактивности. Slack отправляет полезные нагрузки взаимодействий как application/x-www-form-urlencoded с параметром payload, содержащим JSON, и вы должны ответить HTTP 200 в течение 3 секунд (Обработка взаимодействия пользователя).

Это паттерн, лежащий в основе вопроса FAQ о безопасном запуске внутренних действий.

Диаграмма потока взаимодействия со Slack

Диаграмма взаимодействия со Slack

Веб-хук против приложения и механика реализации

Рекомендуемые библиотеки

Go:

Python:

Подход с веб-хуком против бота приложения

Практическое сравнение:

Возможность Входящий веб-хук Приложение Slack с токеном бота
Отправка сообщений Да Да
Отправка макетов Block Kit Да Да
Получение нажатий кнопок Только если привязано к приложению с интерактивностью Да
Слэш-команды Нет Да
Шаги рабочих процессов Нет Да
Поверхность безопасности Тайна URL веб-хука Токены OAuth плюс секрет подписи
Лучшее применение Односторонние оповещения Рабочие процессы, согласования, интерактивный интерфейс

Slack явно поддерживает макеты Block Kit с входящими веб-хуками (Отправка сообщений с использованием входящих веб-хуков). Интерактивность настраивается для каждого приложения и доставляется на URL запроса (Обработка взаимодействия пользователя).

Субъективное мнение: веб-хуки — это отличная первая веха, но как только вы захотите, чтобы Slack стал поверхностью управления, вы строите приложение. Не стоит притворяться обратным.

Области доступа и разрешения

Области доступа Slack определяют, что может делать ваше приложение. Есть центральная справочная страница по областям доступа и отдельные страницы для каждой области (Справочник областей доступа). Для отправки сообщений через Web API канонической областью доступа является chat:write (область доступа chat:write).

Для слэш-команд обычно требуется область доступа commands и настроенный URL запроса команды (команды являются частью документации по интерактивности, и у каждой команды есть свой URL запроса) (Обработка взаимодействия пользователя).

Примечание FAQ: доставка полезной нагрузки кнопок — это «не область доступа», это настройка приложения. Ваше приложение получает полезные нагрузки, когда включена интерактивность и установлен URL запроса, но для обновления сообщений все еще обычно требуется chat:write.

Ограничения частоты и повторные попытки

Ограничения частоты запросов Slack возвращают HTTP 429 и включают Retry-After в секундах, и это применяется к HTTP-базовым API, включая входящие веб-хуки (Ограничения частоты).

На практике:

  • соблюдайте Retry-After
  • применяйте экспоненциальную задержку с дрожанием для временных ошибок 5xx
  • централизуйте доставку Slack в диспетчере, когда объем растет

Идемпотентность и дедупликация

Slack ожидает подтверждения для полезных нагрузок взаимодействия в течение 3 секунд, иначе пользователи видят ошибку, и Slack может повторить доставку в зависимости от функции (Обработка взаимодействия пользователя). Для API событий Slack явно предоставляет заголовки метаданных повторных попыток x-slack-retry-num (API событий).

Даже без явных повторных попыток дубликаты возникают, потому что пользователи дважды кликают и потому что распределенные системы повторяют передачу. Если ваша кнопка запускает внутреннее действие, относитесь к кликам как к событиям «хотя бы один раз» и дедуплицируйте.

Практическая стратегия идемпотентности для согласований:

  • ключ идемпотентности = team_id + channel_id + message_ts + action_id + user_id
  • храните ключ в Redis с TTL, соответствующим вашему временному окну рабочего процесса
  • внутренний API действий также обеспечивает идемпотентность, а не только обработчик Slack

Основы безопасности и проверка запросов

Slack подписывает запросы к вашему серверу, используя секрет подписи вашего приложения. Slack отправляет заголовки X-Slack-Signature и X-Slack-Request-Timestamp, и Slack рекомендует отклонять запросы старше пяти минут для предотвращения атак повторного воспроизведения (Проверка запросов от Slack).

Две ловушки, которые появляются в реальных обзорах кода:

  • Вы должны вычислить подпись над сырым телом запроса, до разбора JSON или декодирования формы (Проверка запросов от Slack).
  • Вы должны подтверждать интерактивные полезные нагрузки в течение 3 секунд, поэтому выполняйте тяжелую работу асинхронно и используйте response_url для передачи результатов (Обработка взаимодействия пользователя).

Python Slack SDK включает утилиту верификатора подписи запроса в коде и документации (верификатор подписи python-slack-sdk).

Проектирование сообщений и взаимодействий

Шаблон сообщения об оповещении

Если вы хотите, чтобы Slack действовал как системный интерфейс, структурируйте свои сообщения так, чтобы решения были очевидны. Шаблон сообщения, который хорошо работает в разных командах:

  • заголовок
  • серьезность
  • контекст
  • подсказка действия
  • ссылки

Минимальный шаблон:

заголовок: повышен уровень ошибок при оформлении заказа серьезность: warn контекст: service=checkout env=prod region=us-east подсказка действия: нажмите Approve restart для запуска безопасной перезагрузки

Пример полезной нагрузки входящего веб-хука

Входящие веб-хуки принимают JSON-полезные нагрузки и могут включать богатые макеты с использованием Block Kit (Отправка сообщений с использованием входящих веб-хуков).

{
  "text": "checkout error rate elevated",
  "blocks": [
    {
      "type": "header",
      "text": { "type": "plain_text", "text": "checkout error rate elevated" }
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": "*severity*\\nwarn" },
        { "type": "mrkdwn", "text": "*context*\\nservice=checkout env=prod region=us-east" }
      ]
    },
    {
      "type": "section",
      "text": { "type": "mrkdwn", "text": "*action_hint*\\nClick Approve to trigger a safe restart." }
    }
  ]
}

Проектирование кнопок и идентификаторов

Кнопки должны находиться внутри блоков section или actions и включать action_id и необязательное значение (Элемент кнопки). action_id — это ваш ключ маршрутизации. value — это ваша полезная нагрузка. Вместе они являются вашей схемой событий.

Субъективное мнение: выбирайте значения action_id как стабильные конечные точки API. Названия вроде “approve_restart” стареют лучше, чем “button_1”.

Обработка полезной нагрузки взаимодействия, response_url и тайминг

Slack отправляет полезные нагрузки взаимодействий на ваш URL запроса как данные, закодированные в формате формы, с параметром payload, содержащим JSON. Полезная нагрузка включает поле type, определяющее источник, например block_actions для нажатий кнопок (Обработка взаимодействия пользователя, Полезные нагрузки взаимодействий).

Вы должны вернуть HTTP 200 в течение 3 секунд для ответа подтверждения (Обработка взаимодействия пользователя). Используйте response_url для обновления исходного сообщения или ответа в канале или в потоке, и Slack ограничивает использование response_url до пяти раз в течение тридцати минут (Обработка взаимодействия пользователя).

Это ограничение по времени является ограничением проектирования. Оно заставляет вас разделять «подтверждение» и «выполнение работы».

Паттерны взаимодействия, подходящие для Slack

  • Кнопки в Block Kit для согласований и ветвления.
  • Слэш-команды для явного намерения пользователя и параметров.
  • Шаги рабочих процессов для повторяемых бизнес-процессов в Workflow Builder (Шаги рабочих процессов).
  • Ярлыки и модальные окна, когда вам нужен структурированный ввод, с ограничениями trigger_id, описанными в документации по интерактивности (Обработка взаимодействия пользователя).

Примеры на Go и Python

Примечание издателя: их можно разделить на отдельные страницы с примерами:

  • /app-architecture/integration-patterns/slack/go-example
  • /app-architecture/integration-patterns/slack/python-example

Примеры приоритезируют одну вещь, которая держит системы стабильными:

  • проверка подписей Slack
  • подтверждение в течение трех секунд
  • дедупликация действий
  • запуск внутреннего HTTP POST
  • опциональное обновление Slack с использованием response_url

Пример на Go: отправка оповещения и обработка согласования кнопки

Предварительные требования:

package main

import (
  "bytes"
  "crypto/hmac"
  "crypto/sha256"
  "encoding/hex"
  "encoding/json"
  "io"
  "log"
  "net/http"
  "net/url"
  "os"
  "strconv"
  "strings"
  "sync"
  "time"

  "github.com/slack-go/slack"
)

type BlockActionPayload struct {
  Type  string `json:"type"`
  Team  struct{ ID string `json:"id"` } `json:"team"`
  User  struct{ ID string `json:"id"` } `json:"user"`
  Channel struct {
    ID string `json:"id"`
  } `json:"channel"`
  Message struct {
    Ts string `json:"ts"`
  } `json:"message"`
  ResponseURL string `json:"response_url"`
  Actions []struct {
    ActionID string `json:"action_id"`
    Value    string `json:"value"`
    Type     string `json:"type"`
  } `json:"actions"`
}

type InternalAction struct {
  Action     string `json:"action"`
  TeamID     string `json:"team_id"`
  ChannelID  string `json:"channel_id"`
  MessageTS  string `json:"message_ts"`
  UserID     string `json:"user_id"`
  Value      string `json:"value"`
}

var (
  // В продакшене храните это в Redis с TTL
  seenMu sync.Mutex
  seen   = map[string]time.Time{}
  ttl    = 10 * time.Minute
)

func main() {
  botToken := os.Getenv("SLACK_BOT_TOKEN")
  signingSecret := os.Getenv("SLACK_SIGNING_SECRET")
  channelID := os.Getenv("SLACK_CHANNEL_ID")
  internalURL := os.Getenv("INTERNAL_API_URL")
  listenAddr := os.Getenv("LISTEN_ADDR") // e.g. :8080

  if botToken == "" || signingSecret == "" || channelID == "" || internalURL == "" || listenAddr == "" {
    log.Fatal("missing env vars SLACK_BOT_TOKEN SLACK_SIGNING_SECRET SLACK_CHANNEL_ID INTERNAL_API_URL LISTEN_ADDR")
  }

  api := slack.New(botToken)

  // Отправьте сообщение об оповещении с кнопкой согласования.
  // Кнопки — это интерактивные элементы Block Kit с action_id и value.
  // См. документацию Slack по элементам кнопок Block Kit.
  blocks := slack.Blocks{
    BlockSet: []slack.Block{
      slack.NewHeaderBlock(slack.NewTextBlockObject("plain_text", "checkout error rate elevated", false, false)),
      slack.NewSectionBlock(
        slack.NewTextBlockObject("mrkdwn", "*severity*\\nwarn\\n*context*\\nservice=checkout env=prod", false, false),
        nil,
        nil,
      ),
      slack.NewActionBlock(
        "actions_1",
        slack.NewButtonBlockElement("approve_restart", "restart", slack.NewTextBlockObject("plain_text", "Approve restart", false, false)),
      ),
    },
  }

  _, ts, err := api.PostMessage(channelID, slack.MsgOptionBlocks(blocks.BlockSet...))
  if err != nil {
    log.Fatalf("PostMessage failed: %v", err)
  }
  log.Printf("posted alert message_ts=%s", ts)

  // Конечная точка интерактивности
  http.HandleFunc("/slack/actions", func(w http.ResponseWriter, r *http.Request) {
    rawBody, err := io.ReadAll(r.Body)
    if err != nil {
      http.Error(w, "read body failed", http.StatusBadRequest)
      return
    }
    r.Body.Close()

    // Проверьте подпись запроса Slack на сыром теле и временной метке.
    // См. документацию Slack по проверке запросов.
    if !verifySlackRequest(r.Header, rawBody, signingSecret) {
      http.Error(w, "invalid signature", http.StatusUnauthorized)
      return
    }

    // Slack отправляет application/x-www-form-urlencoded с payload=JSON
    vals, err := url.ParseQuery(string(rawBody))
    if err != nil {
      http.Error(w, "bad form body", http.StatusBadRequest)
      return
    }
    payloadStr := vals.Get("payload")
    if payloadStr == "" {
      http.Error(w, "missing payload", http.StatusBadRequest)
      return
    }

    var p BlockActionPayload
    if err := json.Unmarshal([]byte(payloadStr), &p); err != nil {
      http.Error(w, "bad payload json", http.StatusBadRequest)
      return
    }

    // Подтвердите в течение 3 секунд. Выполняйте реальную работу асинхронно и используйте response_url для обновлений.
    // См. документацию Slack по интерактивности о времени подтверждения.
    w.WriteHeader(http.StatusOK)
    _, _ = w.Write([]byte(""))

    go func() {
      if p.Type != "block_actions" || len(p.Actions) == 0 {
        return
      }
      a := p.Actions[0]
      if a.ActionID != "approve_restart" {
        return
      }

      // Дедупликация согласований
      key := strings.Join([]string{p.Team.ID, p.Channel.ID, p.Message.Ts, p.User.ID, a.ActionID, a.Value}, "|")
      if !tryOnce(key) {
        return
      }

      req := InternalAction{
        Action:    "approve_restart",
        TeamID:    p.Team.ID,
        ChannelID: p.Channel.ID,
        MessageTS: p.Message.Ts,
        UserID:    p.User.ID,
        Value:     a.Value,
      }

      if err := postJSON(internalURL, req); err != nil {
        log.Printf("internal action failed: %v", err)
        _ = replyViaResponseURL(p.ResponseURL, "action failed, check logs")
        return
      }

      _ = replyViaResponseURL(p.ResponseURL, "approval received, internal action triggered")
    }()
  })

  log.Printf("listening on %s", listenAddr)
  log.Fatal(http.ListenAndServe(listenAddr, nil))
}

func tryOnce(key string) bool {
  now := time.Now()

  seenMu.Lock()
  defer seenMu.Unlock()

  for k, t := range seen {
    if now.Sub(t) > ttl {
      delete(seen, k)
    }
  }
  if _, ok := seen[key]; ok {
    return false
  }
  seen[key] = now
  return true
}

func verifySlackRequest(h http.Header, body []byte, signingSecret string) bool {
  ts := h.Get("X-Slack-Request-Timestamp")
  sig := h.Get("X-Slack-Signature")
  if ts == "" || sig == "" {
    return false
  }

  tsInt, err := strconv.ParseInt(ts, 10, 64)
  if err != nil {
    return false
  }

  // Отклоните запросы старше 5 минут для снижения риска повторного воспроизведения.
  if time.Since(time.Unix(tsInt, 0)) > 5*time.Minute {
    return false
  }

  base := "v0:" + ts + ":" + string(body)
  mac := hmac.New(sha256.New, []byte(signingSecret))
  mac.Write([]byte(base))
  sum := hex.EncodeToString(mac.Sum(nil))
  expected := "v0=" + sum

  return hmac.Equal([]byte(expected), []byte(sig))
}

func postJSON(url string, body any) error {
  b, err := json.Marshal(body)
  if err != nil {
    return err
  }
  req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(b))
  if err != nil {
    return err
  }
  req.Header.Set("Content-Type", "application/json")
  c := &http.Client{Timeout: 5 * time.Second}
  res, err := c.Do(req)
  if err != nil {
    return err
  }
  defer res.Body.Close()
  if res.StatusCode < 200 || res.StatusCode >= 300 {
    return io.ErrUnexpectedEOF
  }
  return nil
}

func replyViaResponseURL(responseURL string, text string) error {
  if responseURL == "" {
    return nil
  }
  // response_url принимает JSON-полезные нагрузки и может отправлять эфемерные сообщения по умолчанию.
  b, _ := json.Marshal(map[string]string{
    "text": text,
  })
  req, _ := http.NewRequest(http.MethodPost, responseURL, bytes.NewReader(b))
  req.Header.Set("Content-Type", "application/json")
  c := &http.Client{Timeout: 5 * time.Second}
  res, err := c.Do(req)
  if err != nil {
    return err
  }
  defer res.Body.Close()
  return nil
}

Пример на Python: отправка оповещения и обработка согласования кнопки

Предварительные требования:

import os
import json
import time
import threading
import requests
from flask import Flask, request, make_response
from slack_sdk import WebClient
from slack_sdk.signature import SignatureVerifier

SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"]
SLACK_SIGNING_SECRET = os.environ["SLACK_SIGNING_SECRET"]
SLACK_CHANNEL_ID = os.environ["SLACK_CHANNEL_ID"]
INTERNAL_API_URL = os.environ["INTERNAL_API_URL"]

client = WebClient(token=SLACK_BOT_TOKEN)
verifier = SignatureVerifier(signing_secret=SLACK_SIGNING_SECRET)

app = Flask(__name__)

# В продакшене храните это в Redis
_seen = {}
_TTL_SECONDS = 600

def try_once(key: str) -> bool:
    now = int(time.time())
    expired = [k for k, t in _seen.items() if now - t > _TTL_SECONDS]
    for k in expired:
        _seen.pop(k, None)
    if key in _seen:
        return False
    _seen[key] = now
    return True

def post_internal_action(payload: dict) -> None:
    requests.post(INTERNAL_API_URL, json=payload, timeout=5)

def reply_via_response_url(response_url: str, text: str) -> None:
    if not response_url:
        return
    requests.post(response_url, json={"text": text}, timeout=5)

def send_alert_with_button() -> None:
    blocks = [
        {"type": "header", "text": {"type": "plain_text", "text": "checkout error rate elevated"}},
        {"type": "section", "text": {"type": "mrkdwn", "text": "*severity*\\nwarn\\n*context*\\nservice=checkout env=prod"}},
        {
            "type": "actions",
            "block_id": "actions_1",
            "elements": [
                {
                    "type": "button",
                    "text": {"type": "plain_text", "text": "Approve restart"},
                    "action_id": "approve_restart",
                    "value": "restart"
                }
            ]
        }
    ]
    # chat.postMessage требует области доступа chat:write.
    client.chat_postMessage(channel=SLACK_CHANNEL_ID, text="checkout error rate elevated", blocks=blocks)

@app.post("/slack/actions")
def slack_actions():
    raw_body = request.get_data()  # Slack рекомендует проверять сырое тело до разбора
    if not verifier.is_valid_request(raw_body, request.headers):
        return make_response("invalid signature", 401)

    # Slack отправляет application/x-www-form-urlencoded с полем payload, содержащим JSON.
    payload_str = request.form.get("payload", "")
    if not payload_str:
        return make_response("missing payload", 400)
    payload = json.loads(payload_str)

    # Подтвердите в течение 3 секунд. Пользователь Slack увидит ошибки, если вы этого не сделаете.
    # См. документацию Slack по интерактивности о подтверждении.
    resp = make_response("", 200)

    def work():
        if payload.get("type") != "block_actions":
            return
        actions = payload.get("actions", [])
        if not actions:
            return

        a = actions[0]
        if a.get("action_id") != "approve_restart":
            return

        team_id = payload.get("team", {}).get("id", "")
        channel_id = payload.get("channel", {}).get("id", "")
        user_id = payload.get("user", {}).get("id", "")
        message_ts = payload.get("message", {}).get("ts", "")
        value = a.get("value", "")

        key = "|".join([team_id, channel_id, message_ts, user_id, "approve_restart", value])
        if not try_once(key):
            return

        internal_payload = {
            "action": "approve_restart",
            "team_id": team_id,
            "channel_id": channel_id,
            "message_ts": message_ts,
            "user_id": user_id,
            "value": value,
        }

        try:
            post_internal_action(internal_payload)
            reply_via_response_url(payload.get("response_url", ""), "approval received, internal action triggered")
        except Exception:
            reply_via_response_url(payload.get("response_url", ""), "action failed, check logs")

    threading.Thread(target=work, daemon=True).start()
    return resp

if __name__ == "__main__":
    send_alert_with_button()
    app.run(host="0.0.0.0", port=int(os.getenv("PORT", "8080")))

Примечания по эксплуатации: маршрутизация, UX, безопасность, ссылки и SEO

Когда использовать Slack, инструменты пейджинга или Discord

Эта страница посвящена механике. Маршрутизация — это стратегия. Тем не менее, границу легко описать.

Канал Лучше всего для Режим сбоя
PagerDuty или аналог Срочное влияние на пользователя, требующее немедленного реагирования Люди спят во время Slack
Slack Координация, согласования, выполнение рабочих процессов Шум и усталость от каналов
Discord Команды, живущие в Discord, более легкие циклы управления Меньше структуры корпоративных рабочих процессов

Используйте Slack, когда вы хотите, чтобы разговор и рабочий процесс были интерфейсом. Используйте инструменты пейджинга, когда оповещение не является опциональным. Если вы балансируете дизайн взаимодействия со Slack, границами сервисов и выбором постоянства, этот обзор архитектуры приложений поможет разместить это решение в более широкой системе. Для более глубокой модели маршрутизации см. Проектирование современных систем оповещения для команд наблюдаемости. Для альтернативы интеграции с Discord см. Паттерн интеграции Discord для оповещений и циклов управления.

Примечания по доступности и UX

  • Помещайте высокообъемные оповещения в свой канал и сохраняйте обсуждения людей в потоках.
  • Используйте потоки для контекста и обновлений по каждому инциденту. response_url может отправлять сообщения в канале и в потоках, если предоставлен thread_ts (Обработка взаимодействия пользователя).
  • Используйте эфемерные ответы при подтверждении действий пользователя, чтобы избежать спама в канале, но помните, что доставка эфемерных сообщений не гарантирована и зависит от сессии (chat.postEphemeral).
  • Используйте Block Kit Builder для быстрой прототипизации макетов (Block Kit).
  • Если вы добавляете изображения, включайте осмысленный альт-текст там, где это поддерживается, и сохраняйте текстовый запасной вариант в поле верхнего уровня text.

Контрольный список безопасности

  • Проверяйте каждый входящий запрос Slack, используя заголовки секрета подписи и сырое тело (Проверка запросов от Slack).
  • Отклоняйте запросы с временными метками старше пяти минут для снижения риска повторного воспроизведения (Проверка запросов от Slack).
  • Храните токены и URL веб-хуков в менеджере секретов, никогда в git.
  • Используйте минимально необходимые OAuth-области доступа и меняйте секреты при смене ролей людей (Справочник областей доступа).
  • Аутентифицируйте и авторизуйте внутренний API действий отдельно, не рассматривайте Slack как границу аутентификации.
  • Делайте согласования идемпотентными и дедуплицированными.
  • Вести журнал согласований удобным для аудита образом, включая команду, канал, временную метку сообщения, пользователя и действие.

Заключение

Slack показывает лучшие результаты, когда вы относитесь к нему как к системной границе, а не к приемнику сообщений. Входящие веб-хуки покрывают быструю доставку оповещений. Приложения плюс интерактивность превращают Slack в движок рабочих процессов и интерфейс событий. Сложные части — это проверка подписей, временные ограничения, дедупликация и выбор того, где Slack вписывается в вашу модель маршрутизации оповещений.

Следующие ссылки: