Wzorce integracji Slack dla alertów i przepływów pracy

Slack to interfejs użytkownika do przepływów pracy oraz warstwa dostarczania alertów.

Page content

Integracje ze Slackiem wydają się na pierwszy rzut oka oszaczająco proste, ponieważ wiadomość można wysłać w jednym żądaniu HTTP. Zainteresujące rzeczy zaczynają się wtedy, gdy chcesz, aby Slack był interaktywny i niezawodny.

Slack Integration Alerts

Ta szczegółowa analiza traktuje Slacka jako trzy różne powierzchnie integracyjne:

  • Zbiornik powiadomień dla jednostronnych alertów za pomocą przychodzących webhooków.
  • Silnik przepływów pracy (workflow) za pomocą Workflow Buildera i niestandardowych kroków przepływu.
  • Interfejs zdarzeń za pomocą przycisków Block Kit, poleceń slash oraz ładunków akcji (action payloads).

Ta strona opisuje, jak systemy przekraczają granicę do wspólnego interfejsu użytkownika, który może również emitować zdarzenia z powrotem do Twojej architektury, a nie filozofię alertów. Dla strategii alertowania i routingu zobacz Współczesny projekt systemów alertowania dla zespołów obserwowalności.

Powiązane lektury:

Kanoniczne ujęcie i umiejscowienie we wzorcach integracji

Slack to nie tylko miejsce, gdzie alerty umierają. Użyty mądrze, Slack staje się interfejsem systemowym, w którym wiadomości są artefaktami z stanem, a interakcje użytkownika stanowią zdarzenia.

Ta strona kanonicznie znajduje się pod /app-architecture/integration-patterns/slack/, ponieważ głównym pytaniem nie jest „czy powinniśmy generować alerty", ale „jaki jest kontrakt między naszym systemem a Slackiem".

Jeśli Twoje rozwiązanie wymaga któregokolwiek z poniższych, znajdujesz się w terenie wzorców integracji, a nie prostych powiadomień:

  • Pętli decyzyjnej, gdzie zatwierdzenie przez człowieka blokuje akcję.
  • Przepływu pracy (workflow), gdzie Slack zbiera kontekst i uruchamia kroki.
  • Pętli zdarzeniowej, gdzie Slack emituje akcje, do których Twój system subskrybuje.

Platforma Slack intencjonalnie obsługuje zarówno jednostronną wymianę wiadomości, jak i dwukierunkową interakcję poprzez URL żądań i ładunki interakcji. Przychodzące webhooke są pierwszym sposobem na publikowanie ładunków JSON, w tym elementów budulcowych Block Kit, na kanał (Wysyłanie wiadomości przy użyciu przychodzących webhooków). Interaktywność jest dostarczana do Twojej aplikacji jako żądania HTTP POST do skonfigurowanego URL żądania, a te ładunki są kodowane jako formularz z polem JSON (Obsługa interakcji użytkownika).

Slack jako zbiornik powiadomień

Przychodzące webhooke to najszybsza droga do wartości dla alertów i aktualizacji statusu. Webhook to unikalny URL powiązany z instalacją aplikacji, do którego wysyłasz wiadomość JSON (Wysyłanie wiadomości przy użyciu przychodzących webhooków).

Opiniowany pogląd: webhooke są doskonałym domyślnym wyborem, gdy chcesz wysyłać wiadomości „wyślij i zapomnij" i nie potrzebujesz, aby Slack był powierzchnią sterowania. Webhooke są również doskonałym sposobem na odłączenie onboardingu od Twojej ostatecznej architektury aplikacji.

Slack jako silnik przepływów pracy

Workflow Builder istnieje, ponieważ praca dzieje się w czacie. Przepływy pracy mogą być proste lub złożone i mogą być podłączane do aplikacji (Poradnik Workflow Buildera).

Niestandardowe kroki przepływu pracy pozwalają na wystawienie Twoich systemów jako ponownie używane bloki budulcowe wewnątrz Workflow Buildera (Kroki przepływu pracy). Jest to inny kształt integracji niż boty na kanałach. Przenosi to Twoją integrację bliżej „narzędzi wewnątrz Slacka" niż „wiadomości z zewnątrz".

Opiniowany pogląd: jeśli Twoja organizacja już myśli w kategoriach przepływów pracy i zatwierdzeń, kroki przepływu pracy mogą wydawać się bardziej naturalne niż dedykowane boty.

Slack jako interfejs zdarzeń

Block Kit zamienia wiadomość w powierzchnię UI (Block Kit). Interaktywne komponenty, takie jak przyciski, generują ładunki akcji, zazwyczaj ładunki block_actions, które są wysyłane do Twojej aplikacji, gdy użytkownik kliknie (ładunek block_actions).

Przyciski mają jawnie zdefiniowane identyfikatory action_id i opcjonalną wartość, a muszą być umieszczone w blokach section lub actions (Element przycisku). Kiedy projektujesz wiadomość z przyciskiem, projektujesz źródło zdarzeń.

To tutaj tematy FAQ, takie jak weryfikacja żądań, wymagane zakresy (scopes) i bezpieczne wyzwalacze wewnętrzne, stają się centrum projektu.

Wzorce architektury, które skalują się

Przepływ webhooka dla jednostronnego alertowania

[service] -> [alert formatter] -> [slack incoming webhook] -> [channel]

Przychodzące webhooke akceptują ładunki JSON i obsługują układy Block Kit (Wysyłanie wiadomości przy użyciu przychodzących webhooków).

Kiedy FAQ pyta o najszybszy sposób wysyłania alertów, zazwyczaj jest to ten sposób.

Przepływ z brokerem z kolejką dla niezawodności i przeciwdziałania przeciążeniom

[services] -> [queue topic] -> [slack dispatcher] -> [slack api]
                     |                 |
                     |                 +-> [rate limit handler]
                     +-> [dead letter queue]

Limity współczynników (rate limits) Slacka dotyczą API opartych na HTTP, w tym przychodzących webhooków, a Slack zwraca HTTP 429 z nagłówkiem Retry-After, gdy przekraczasz limity (Limity współczynników).

Opiniowany pogląd: jeśli wysyłasz alerty bezpośrednio z każdej usługi, pierwszy incydent zamienia się w rozproszony atak zaprzepaszenia usługi (DDoS) na Twoją własną integrację ze Slackiem. Dyspozyter za kolejką zazwyczaj tworzy spokojniejszą architekturę.

Wzór automatyzacji przepływu pracy z zatwierdzeniami

[alert] -> [slack message with button] -> [button click]
   -> [action payload] -> [approval handler] -> [internal API] -> [update message]

Interaktywność Slacka wymaga skonfigurowania URL żądania (Request URL) i włączenia interaktywności. Slack wysyła ładunki interakcji jako application/x-www-form-urlencoded z parametrem payload zawierającym JSON, a musisz odpowiedzieć HTTP 200 w ciągu 3 sekund (Obsługa interakcji użytkownika).

Jest to wzór stojący za elementem FAQ dotyczącym bezpiecznego uruchamiania wewnętrznych akcji.

Wykres przepływu interakcji ze Slackiem

Slack interaction diagram

Webhook vs aplikacja i mechanika implementacji

Zalecane biblioteki

Go:

Python:

Podejście webhooka vs bota aplikacji

Praktyczne porównanie:

Funkcja Przychodzący webhook Aplikacja Slacka z tokenem bota
Publikacja wiadomości Tak Tak
Publikacja układów Block Kit Tak Tak
Otrzymywanie kliknięć przycisków Tylko jeśli powiązany z aplikacją z interaktywnością Tak
Polecenia slash Nie Tak
Kroki przepływu pracy Nie Tak
Powierzchnia bezpieczeństwa Tajemnica URL webhooka Tokeny OAuth plus sekret podpisu
Najlepsze dopasowanie Jednostronne alerty Przepływy pracy, zatwierdzenia, interaktywne UI

Slack jawnie obsługuje układy Block Kit z przychodzącymi webhookami (Wysyłanie wiadomości przy użyciu przychodzących webhooków). Interaktywność jest konfigurowana dla każdej aplikacji i dostarczana do URL żądania (Obsługa interakcji użytkownika).

Opiniowany pogląd: webhooke są świetnym pierwszym kamieniem milowym, ale tak soon, jak chcesz, aby Slack był powierzchnią sterowania, budujesz aplikację. Nie udawaj inaczej.

Zakresy i uprawnienia

Zakresy (scopes) Slacka definiują, co Twoja aplikacja może robić. Istnieje centralne odniesienie zakresów i strony dla poszczególnych zakresów (Odniesienie zakresów). Dla wysyłania wiadomości przez Web API, chat:write jest kanonicznym zakresem (zakres chat:write).

Dla poleceń slash zazwyczaj potrzebujesz zakresu commands i skonfigurowanego URL żądania polecenia (polecenia są częścią dokumentacji interaktywności, a każde polecenie ma swój własny URL żądania) (Obsługa interakcji użytkownika).

Uwaga FAQ: dostarczanie ładunku przycisku nie jest „zakresem", to ustawienie aplikacji. Twoja aplikacja otrzymuje ładunki, gdy interaktywność jest włączona i URL żądania jest ustawiony, ale aktualizacja wiadomości nadal zazwyczaj wymaga chat:write.

Limity współczynników i ponawianie

Limity współczynników Slacka zwracają HTTP 429 i zawierają Retry-After w sekundach, a dotyczy to API opartych na HTTP, w tym przychodzących webhooków (Limity współczynników).

W praktyce:

  • szanuj Retry-After
  • stosuj cofanie (backoff) z jitterem dla przejściowych błędów 5xx
  • centralizuj dostarczanie do Slacka w dyspozyterze, gdy rośnie wolumen

Idempotentność i deduplikacja

Slack oczekuje potwierdzenia dla ładunków interakcji w ciągu 3 sekund, w przeciwnym razie użytkownicy widzą błąd, a Slack może ponowić dostarczenie w zależności od funkcji (Obsługa interakcji użytkownika). Dla API Zdarzeń Slack jawnie dostarcza nagłówki metadanych ponowienia x-slack-retry-num (API Zdarzeń).

Nawet bez jawnego ponawiania, duplikaty występują, ponieważ użytkownicy dwukrotnie klikają i ponieważ systemy rozproszone przesyłają ponownie. Jeśli Twój przycisk uruchamia wewnętrzną akcję, traktuj kliknięcia jako zdarzenia „co najmniej raz" i deduplikuj.

Praktyczna strategia idempotentności dla zatwierdzeń:

  • klucz idempotentności = team_id + channel_id + message_ts + action_id + user_id
  • przechowuj klucz w Redis z TTL pasującym do okna przepływu pracy
  • wewnętrzne API akcji również wymusza idempotentność, nie tylko handler Slacka

Podstawy bezpieczeństwa i weryfikacja żądań

Slack podpisuje żądania do Twojego serwera używając Twojego sekretu podpisu aplikacji. Slack wysyła nagłówki X-Slack-Signature i X-Slack-Request-Timestamp, a Slack zaleca odrzucanie żądań starszych niż pięć minut, aby zapobiec atakom odtwarzania (Weryfikacja żądań z Slacka).

Dwa pułapki, które pojawiają się w prawdziwych recenzjach kodu:

  • Musisz obliczyć podpis nad surowym ciałem żądania, przed parsowaniem JSON lub dekodowaniem formularza (Weryfikacja żądań z Slacka).
  • Musisz potwierdzić interaktywne ładunki w ciągu 3 sekund, więc wykonuj ciężką pracę asynchronicznie i używaj response_url do komunikowania wyników (Obsługa interakcji użytkownika).

Python Slack SDK zawiera narzędzie weryfikujące podpis żądania w kodzie i dokumentacji (python-slack-sdk signature verifier).

Projekt wiadomości i interakcji

Szablon wiadomości alertu

Jeśli chcesz, aby Slack działał jak interfejs systemowy, strukturuj wiadomości tak, aby decyzje były oczywiste. Szablon wiadomości, który dobrze działa w różnych zespołach:

  • tytuł
  • powaga
  • kontekst
  • sugestia akcji
  • linki

Minimalny szablon:

tytuł: wysoka stopa błędów w checkout powaga: warn kontekst: service=checkout env=prod region=us-east sugestia akcji: kliknij Approve restart, aby uruchomić bezpieczny restart

Przykład ładunku przychodzącego webhooka

Przychodzące webhooke akceptują ładunki JSON i mogą zawierać bogate układy używające Block Kit (Wysyłanie wiadomości przy użyciu przychodzących webhooków).

{
  "text": "wysoka stopa błędów w checkout",
  "blocks": [
    {
      "type": "header",
      "text": { "type": "plain_text", "text": "wysoka stopa błędów w checkout" }
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": "*powaga*\\nwarn" },
        { "type": "mrkdwn", "text": "*kontekst*\\nservice=checkout env=prod region=us-east" }
      ]
    },
    {
      "type": "section",
      "text": { "type": "mrkdwn", "text": "*sugestia akcji*\\nKliknij Approve, aby uruchomić bezpieczny restart." }
    }
  ]
}

Projektowanie przycisków i identyfikatorów

Przyciski muszą znajdować się w blokach section lub actions i zawierać action_id oraz opcjonalną wartość (Element przycisku). action_id jest Twoim kluczem routingu. value jest Twoim ładunkiem. Razem stanowią one Twój schemat zdarzeń.

Opiniowany pogląd: wybieraj wartości action_id jak stabilne punkty końcowe API. Nazwy takie jak „approve_restart" starzeją się lepiej niż „button_1".

Obsługa ładunków interakcji, response_url i timing

Slack wysyła ładunki interakcji do Twojego URL żądania jako dane zakodowane formularza z parametrem payload zawierającym JSON. Ładunek zawiera pole type definiujące źródło, takie jak block_actions dla kliknięć przycisków (Obsługa interakcji użytkownika, Ładunki interakcji).

Musisz zwrócić HTTP 200 w ciągu 3 sekund dla odpowiedzi potwierdzenia (Obsługa interakcji użytkownika). Użyj response_url do aktualizacji oryginalnej wiadomości lub odpowiedzi na kanału lub w wątku, a Slack ogranicza użycie response_url do pięciu razy w ciągu trzydziestu minut (Obsługa interakcji użytkownika).

To ograniczenie czasowe jest ograniczeniem projektowym. Zmusza Cię do odłączenia „potwierdzenia" od „wykonania pracy".

Wzorce interakcji pasujące do Slacka

  • Przyciski w Block Kit dla zatwierdzeń i rozgałęziania.
  • Polecenia slash dla jawnego zamiaru użytkownika i parametrów.
  • Kroki przepływu pracy dla powtarzalnych procesów biznesowych w Workflow Builder (Kroki przepływu pracy).
  • Skróty i okna modalne, gdy potrzebujesz strukturalnego wprowadzenia, z ograniczeniami trigger_id opisanymi w dokumentacji interaktywności (Obsługa interakcji użytkownika).

Przykłady w Go i Pythonie

Uwaga wydawcy: te mogą być podzielone na dedykowane strony przykładów:

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

Przykłady priorytetyzują jedną rzecz, która utrzymuje systemy stabilne:

  • weryfikuj podpisy Slacka
  • potwierdzaj w ciągu trzech sekund
  • deduplikuj akcje
  • uruchamiaj wewnętrzne żądanie HTTP POST
  • opcjonalnie aktualizuj Slacka używając response_url

Przykład Go: wyślij alert i obsłuż zatwierdzenie przycisku

Wymagania wstępne:

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 (
  // W produkcji przechowuj to w Redis z 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") // np. :8080

  if botToken == "" || signingSecret == "" || channelID == "" || internalURL == "" || listenAddr == "" {
    log.Fatal("brak zmiennych środowiskowych SLACK_BOT_TOKEN SLACK_SIGNING_SECRET SLACK_CHANNEL_ID INTERNAL_API_URL LISTEN_ADDR")
  }

  api := slack.New(botToken)

  // Wyślij wiadomość alertu z przyciskiem zatwierdzenia.
  // Przyciski są interaktywnymi elementami Block Kit z action_id i value.
  // Zobacz dokumentację elementu przycisku Block Kit Slacka.
  blocks := slack.Blocks{
    BlockSet: []slack.Block{
      slack.NewHeaderBlock(slack.NewTextBlockObject("plain_text", "wysoka stopa błędów w checkout", false, false)),
      slack.NewSectionBlock(
        slack.NewTextBlockObject("mrkdwn", "*powaga*\\nwarn\\n*kontekst*\\nservice=checkout env=prod", false, false),
        nil,
        nil,
      ),
      slack.NewActionBlock(
        "actions_1",
        slack.NewButtonBlockElement("approve_restart", "restart", slack.NewTextBlockObject("plain_text", "Zatwierdź 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)

  // Endpoint interaktywności
  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()

    // Zweryfikuj podpis żądania Slacka na surowym ciele i znaczniku czasu.
    // Zobacz dokumentację weryfikacji żądań Slacka.
    if !verifySlackRequest(r.Header, rawBody, signingSecret) {
      http.Error(w, "invalid signature", http.StatusUnauthorized)
      return
    }

    // Slack wysyła application/x-www-form-urlencoded z 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
    }

    // Potwierdź w ciągu 3 sekund. Wykonuj prawdziwą pracę asynchronicznie i używaj response_url do aktualizacji.
    // Zobacz dokumentację interaktywności Slacka dotyczącą czasu potwierdzenia.
    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
      }

      // Deduplikuj zatwierdzenia
      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
  }

  // Odrzuć żądania starsze niż 5 minut, aby zmniejszyć ryzyko odtworzenia.
  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 akceptuje ładunki JSON i może publikować efemeryczne domyślnie.
  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
}

Przykład Pythona: wyślij alert i obsłuż zatwierdzenie przycisku

Wymagania wstępne:

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__)

# W produkcji przechowuj te w 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": "wysoka stopa błędów w checkout"}},
        {"type": "section", "text": {"type": "mrkdwn", "text": "*powaga*\\nwarn\\n*kontekst*\\nservice=checkout env=prod"}},
        {
            "type": "actions",
            "block_id": "actions_1",
            "elements": [
                {
                    "type": "button",
                    "text": {"type": "plain_text", "text": "Zatwierdź restart"},
                    "action_id": "approve_restart",
                    "value": "restart"
                }
            ]
        }
    ]
    # chat.postMessage wymaga zakresu chat:write.
    client.chat_postMessage(channel=SLACK_CHANNEL_ID, text="wysoka stopa błędów w checkout", blocks=blocks)

@app.post("/slack/actions")
def slack_actions():
    raw_body = request.get_data()  # Slack zaleca weryfikację surowego ciała przed parsowaniem
    if not verifier.is_valid_request(raw_body, request.headers):
        return make_response("invalid signature", 401)

    # Slack wysyła application/x-www-form-urlencoded z polem payload zawierającym JSON.
    payload_str = request.form.get("payload", "")
    if not payload_str:
        return make_response("missing payload", 400)
    payload = json.loads(payload_str)

    # Potwierdź w ciągu 3 sekund. Użytkownik Slacka widzi błędy, jeśli tego nie zrobisz.
    # Zobacz dokumentację interaktywności Slacka dotyczącą potwierdzenia.
    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")))

Uwagi operacyjne: routing, UX, bezpieczeństwo, linki i SEO

Kiedy używać Slacka vs narzędzi do powiadamiania vs Discorda

Ta strona dotyczy mechaniki. Routing to strategia. Nadal, granica jest łatwa do opisania.

Kanał Najlepsze dla Tryb awarii
PagerDuty lub równoważne Pilne wpływy na użytkownika wymagające natychmiastowej reakcji Ludzie śpią przez Slack
Slack Koordynacja, zatwierdzenia, wykonywanie przepływów pracy Szum i zmęczenie kanału
Discord Zespoły żyjące w Discordzie, lżejsze pętle sterowania Mniej struktury przepływów pracy enterprise

Używaj Slacka, gdy chcesz, aby rozmowa i przepływ pracy były interfejsem. Używaj narzędzi do powiadamiania, gdy alert nie jest opcjonalny. Jeśli баланsujesz projektowanie interakcji Slacka z granicami usług i wyborami trwałości, ten przegląd architektury aplikacji pomaga umiejscowić tę decyzję w większym systemie. Dla głębszego modelu routingu zobacz Współczesny projekt systemów alertowania dla zespołów obserwowalności. Dla alternatywy integracji Discorda zobacz Wzór integracji Discorda dla alertów i pętli sterowania.

Uwagi dotyczące dostępności i UX

  • Wprowadzaj alerty o dużym wolumenie do własnego kanału i utrzymuj dyskusje ludzkie w wątkach.
  • Używaj wątków dla kontekstu i aktualizacji na incydent. response_url może publikować na kanału i w wątkach, gdy podany jest thread_ts (Obsługa interakcji użytkownika).
  • Używaj odpowiedzi efemerycznych przy potwierdzaniu akcji użytkownika, aby uniknąć spamu na kanale, ale pamiętaj, że dostarczanie efemeryczne nie jest gwarantowane i zależy od sesji (chat.postEphemeral).
  • Używaj Block Kit Builder do szybkiego prototypowania układów (Block Kit).
  • Jeśli dodajesz obrazy, dołącz znaczący tekst alternatywny, gdzie jest obsługiwany, i zachowaj zapasowy tekst zwykły w polu tekstowym najwyższego poziomu.

Kontrola bezpieczeństwa

  • Weryfikuj każde przychodzące żądanie Slacka używając nagłówków sekretu podpisu i surowego ciała (Weryfikacja żądań z Slacka).
  • Odrzucaj żądania ze znacznikami czasu starszymi niż pięć minut, aby zmniejszyć ryzyko odtworzenia (Weryfikacja żądań z Slacka).
  • Przechowuj tokeny i URL webhooks w menedżerze tajemnic, nigdy w git.
  • Używaj zakresów OAuth o najmniejszych uprawnieniach i rotuj sekrety, gdy ludzie zmieniają role (Odniesienie zakresów).
  • Autoryzuj i uwierzytelnij wewnętrzne API akcji oddzielnie, nie traktuj Slacka jako granicy uwierzytelniania.
  • Spraw, aby zatwierdzenia były idempotentne i deduplikowane.
  • Loguj zatwierdzenia w sposób przyjazny dla audytu, w tym zespół, kanał, znacznik czasu wiadomości, użytkownika i akcję.

Podsumowanie

Slack jest w swojej najlepszej formie, gdy traktujesz go jako granicę systemową, a nie zbiornik wiadomości. Przychodzące webhooke pokrywają szybką dostawę alertów. Aplikacje plus interaktywność zamieniają Slacka w silnik przepływów pracy i interfejs zdarzeń. Trudne części to weryfikacja podpisu, ograniczenia czasowe, deduplikacja i wybór, gdzie Slack pasuje w Twoim modelu routingu alertów.

Następne linki: