Slack-Integrationsmuster für Warnungen und Workflows
Slack ist eine Workflow-Oberfläche und eine Schicht für die Alert-Lieferung.
Slack-Integrationen täuschen oft durch ihre Einfachheit, da Sie eine Nachricht mit einer einzigen HTTP-Anfrage senden können. Der interessante Teil beginnt, wenn Sie Slack interaktiv und zuverlässig machen möchten.

Dieser vertiefte Artikel behandelt Slack als drei verschiedene Integrationsoberflächen:
- Benachrichtigungsspeicher für einseitige Warnungen über eingehende Webhooks.
- Workflow-Engine über Workflow Builder und benutzerdefinierte Workflow-Schritte.
- Ereignisschnittstelle über Block Kit-Schaltflächen, Slash-Befehle und Aktions-Payloads.
Diese Seite beschreibt, wie Systeme die Grenze zu einer gemeinsamen Benutzeroberfläche überschreiten, die ihrerseits Ereignisse zurück in Ihre Architektur senden kann, und nicht über die Philosophie von Warnungen. Für Warnstrategien und Routing siehe Design moderner Warnsysteme für Observability-Teams.
Lesen Sie auch:
- Chat-Plattformen als System-Schnittstellen in modernen Systemen
- Discord-Integration Pattern für Warnungen und Regelkreise
- Design moderner Warnsysteme für Observability-Teams
Kanonische Einordnung und Platzierung in Integrationsmustern
Slack ist nicht nur der Ort, an dem Warnungen sterben. Wenn es gut genutzt wird, wird Slack zu einer System-Schnittstelle, in der Nachrichten zustandsbehaftete Artefakte sind und Benutzerinteraktionen Ereignisse darstellen.
Diese Seite ist kanonisch unter /app-architecture/integration-patterns/slack/ platziert, weil die Hauptfrage nicht „sollen wir warnen" ist, sondern „welcher Vertrag besteht zwischen unserem System und Slack".
Wenn Ihre Lösung eines der folgenden Elemente erfordert, befinden Sie sich im Bereich der Integrationsmuster und nicht im Bereich einfacher Benachrichtigungen:
- Eine Entscheidungs-Schleife, bei der eine menschliche Genehmigung eine Aktion freigibt.
- Ein Workflow, bei dem Slack Kontext sammelt und Schritte auslöst.
- Eine Ereignisschleife, bei der Slack Aktionen aussendet, auf die Ihr System abonniert.
Die Slack-Plattform unterstützt absichtlich sowohl einseitige Nachrichten als auch bidirektionale Interaktionen über Request-URLs und Interaktions-Payloads. Eingehende Webhooks sind eine primäre Methode, um JSON-Payloads, einschließlich Block Kit-Bausteinen, in einen Kanal zu senden (Nachrichten senden mit eingehenden Webhooks). Interaktivität wird an Ihre App als HTTP-POST-Anfragen an eine konfigurierte Request-URL zurückgegeben, und diese Payloads sind als Formular kodiert mit einem JSON-Payload-Feld (Verarbeitung von Benutzerinteraktionen).
Slack als Benachrichtigungsspeicher
Eingehende Webhooks sind der schnellste Weg, um Wert für Warnungen und Statusaktualisierungen zu generieren. Ein Webhook ist eine eindeutige URL, die an eine App-Installation gebunden ist, und Sie senden eine JSON-Nachricht daran (Nachrichten senden mit eingehenden Webhooks).
Meine persönliche Meinung: Webhooks sind ein ausgezeichneter Standardwert, wenn Sie „senden und vergessen"-Nachrichten wünschen und Sie nicht benötigen, dass Slack eine Steuerungsoberfläche darstellt. Webhooks sind auch ein ausgezeichneter Weg, um Ihre Onboarding-Prozesse von Ihrer späteren App-Architektur zu entkoppeln.
Slack als Workflow-Engine
Der Workflow Builder existiert, weil Chat der Ort ist, an dem Arbeit tatsächlich stattfindet. Workflows können einfach oder komplex sein und können mit Apps verbunden werden (Leitfaden zum Workflow Builder).
Benutzerdefinierte Workflow-Schritte ermöglichen es Ihnen, Ihre Systeme als wiederverwendbare Bausteine innerhalb des Workflow Builders verfügbar zu machen (Workflow-Schritte). Dies ist eine andere Integrationsform als Bots in Kanälen. Es rückt Ihre Integration näher an „Werkzeuge innerhalb von Slack" heran als an „Nachrichten von außen".
Meine persönliche Meinung: Wenn Ihre Organisation bereits in Workflows und Genehmigungen denkt, können Workflow-Schritte natürlicher wirken als maßgeschneiderte Bots.
Slack als Ereignisschnittstelle
Block Kit verwandelt eine Nachricht in eine Benutzeroberfläche (Block Kit). Interaktive Komponenten wie Schaltflächen generieren Aktions-Payloads, typischerweise block_actions-Payloads, die an Ihre App gesendet werden, wenn ein Benutzer klickt (block_actions-Payload).
Schaltflächen haben explizite Identifikatoren action_id und einen optionalen value und müssen in section- oder actions-Blöcken gehostet werden (Schaltflächen-Element). Wenn Sie eine Nachricht mit einer Schaltfläche entwerfen, entwerfen Sie eine Ereignisquelle.
Hier werden FAQ-Themen wie Anfrageverifizierung, erforderliche Berechtigungen (Scopes) und sichere interne Trigger zum Zentrum des Designs.
Architekturmuster, die skalieren
Webhook-Flow für einseitige Warnungen
[Dienst] -> [Warnformatierer] -> [Slack eingehender Webhook] -> [Kanal]
Eingehende Webhooks akzeptieren JSON-Payloads und unterstützen Block Kit-Layouts (Nachrichten senden mit eingehenden Webhooks).
Wenn die FAQ nach dem schnellsten Weg zum Senden von Warnungen fragt, ist dies meist die Antwort.
Broker-Flow mit einer Warteschlange für Zuverlässigkeit und Backpressure
[Dienste] -> [Warteschlangen-Topic] -> [Slack-Dispatcher] -> [Slack-API]
| |
| +-> [Rate-Limit-Handler]
+-> [Dead-Letter-Queue]
Slack-Rate-Limits gelten für HTTP-basierte APIs, einschließlich eingehender Webhooks, und Slack gibt HTTP 429 mit einem Retry-After-Header zurück, wenn Sie die Limits überschreiten (Rate Limits).
Meine persönliche Meinung: Wenn Sie Warnungen direkt von jedem Dienst senden, wird der erste Vorfall zu einer verteilten Denial-of-Service-Angriff gegen Ihre eigene Slack-Integration. Ein Dispatcher hinter einer Warteschlange neigt dazu, eine ruhigere Architektur zu sein.
Workflow-Automatisierungsmuster mit Genehmigungen
[Warnung] -> [Slack-Nachricht mit Schaltfläche] -> [Schaltflächen-Klick]
-> [Aktions-Payload] -> [Genehmigungs-Handler] -> [interne API] -> [Nachricht aktualisieren]
Slack-Interaktivität erfordert die Konfiguration einer Request-URL und die Aktivierung der Interaktivität. Slack sendet Interaktions-Payloads als application/x-www-form-urlencoded mit einem payload-Parameter, der JSON enthält, und Sie müssen innerhalb von 3 Sekunden mit HTTP 200 antworten (Verarbeitung von Benutzerinteraktionen).
Dies ist das Muster hinter dem FAQ-Eintrag über das sichere Auslösen interner Aktionen.
Slack-Interaktionsflussdiagramm

Webhook vs. App und Implementierungsmechaniken
Empfohlene Bibliotheken
Go:
slack-go/slackfür Web-API und Block Kit-Strukturen (slack-go/slack Repo, pkg.go.dev Docs)
Python:
slack_sdk(Python Slack SDK) für Web-API-Clients, Signatur-Hilfen und Retry-Infrastruktur (Python Slack SDK Docs, python-slack-sdk Repo)
Webhook vs. App-Bot-Ansatz
Ein praktischer Vergleich:
| Fähigkeit | Eingehender Webhook | Slack-App mit Bot-Token |
|---|---|---|
| Nachrichten senden | Ja | Ja |
| Block Kit-Layouts senden | Ja | Ja |
| Schaltflächen-Klicks empfangen | Nur, wenn an eine App mit Interaktivität gebunden | Ja |
| Slash-Befehle | Nein | Ja |
| Workflow-Schritte | Nein | Ja |
| Sicherheitsfläche | Geheimhaltung der Webhook-URL | OAuth-Tokens plus Signing Secret |
| Bestes Einsatzgebiet | Einseitige Warnungen | Workflows, Genehmigungen, interaktive UI |
Slack unterstützt explizit Block Kit-Layouts mit eingehenden Webhooks (Nachrichten senden mit eingehenden Webhooks). Interaktivität wird pro App konfiguriert und an eine Request-URL geliefert (Verarbeitung von Benutzerinteraktionen).
Meine persönliche Meinung: Webhooks sind ein großartiger erster Meilenstein, aber sobald Sie möchten, dass Slack eine Steuerungsoberfläche ist, bauen Sie eine App. Tun Sie nicht so, als wäre es anders.
Berechtigungen und Scopes
Slack-Scopes definieren, was Ihre App tun kann. Es gibt eine zentrale Scopes-Referenz und einzelne Scope-Seiten (Scopes Referenz). Für das Senden von Nachrichten über die Web-API ist chat:write der kanonische Scope (chat:write Scope).
Für Slash-Befehle benötigen Sie typischerweise den commands-Scope und eine konfigurierte Command-Request-URL (Befehle sind Teil der Interaktivitäts-Dokumentation, und jeder Befehl hat seine eigene Request-URL) (Verarbeitung von Benutzerinteraktionen).
FAQ-Hinweis: Die Lieferung von Schaltflächen-Payloads ist „kein Scope", es ist eine App-Einstellung. Ihre App empfängt Payloads, wenn Interaktivität aktiviert ist und eine Request-URL gesetzt ist, aber das Senden von Nachrichtenaktualisierungen erfordert im Allgemeinen immer noch chat:write.
Rate Limits und Retries
Slack-Rate-Limits geben HTTP 429 zurück und enthalten Retry-After in Sekunden, und dies gilt für HTTP-basierte APIs, einschließlich eingehender Webhooks (Rate Limits).
In der Praxis:
Retry-Afterbeachten- Backoff mit Jitter für vorübergehende 5xx-Fehler anwenden
- Slack-Lieferung in einem Dispatcher zentralisieren, wenn das Volumen wächst
Idempotenz und Deduplizierung
Slack erwartet eine Bestätigung für Interaktions-Payloads innerhalb von 3 Sekunden, sonst sehen Benutzer einen Fehler und Slack kann das Lieferverhalten je nach Funktion erneut versuchen (Verarbeitung von Benutzerinteraktionen). Für die Events API stellt Slack explizit Retry-Metadaten-Header x-slack-retry-num bereit (Events API).
Auch ohne explizite Retries treten Duplikate auf, weil Benutzer doppelt klicken und weil verteilte Systeme erneut übertragen. Wenn Ihre Schaltfläche eine interne Aktion auslöst, behandeln Sie Klicks als „mindestens einmal"-Ereignisse und deduplizieren Sie.
Eine praktische Idempotenzstrategie für Genehmigungen:
- Idempotenz-Schlüssel =
team_id+channel_id+message_ts+action_id+user_id - Schlüssel in Redis mit TTL speichern, die Ihrem Workflow-Fenster entspricht
- Die interne Aktions-API erzwingt ebenfalls Idempotenz, nicht nur der Slack-Handler
Sicherheitsgrundlagen und Anfrageverifizierung
Slack signiert Anfragen an Ihren Server unter Verwendung Ihres App-Signing-Secrets. Slack sendet die Header X-Slack-Signature und X-Slack-Request-Timestamp, und Slack empfiehlt, Anfragen, die älter als fünf Minuten sind, abzulehnen, um Replay-Angriffe zu verhindern (Verifizierung von Anfragen von Slack).
Zwei Fallstricke, die in echten Code-Reviews auftreten:
- Sie müssen die Signatur über den rohen Request-Body berechnen, bevor JSON-Parsing oder Formular-Dekodierung (Verifizierung von Anfragen von Slack).
- Sie müssen interaktive Payloads innerhalb von 3 Sekunden bestätigen, also führen Sie schwere Arbeiten asynchron aus und verwenden Sie
response_url, um Ergebnisse mitzuteilen (Verarbeitung von Benutzerinteraktionen).
Das Python Slack SDK enthält eine Anfrage-Signatur-Verifizierer-Nutzung in Code und Docs (python-slack-sdk signature verifier).
Design von Nachrichten und Interaktionen
Vorlage für Warnnachrichten
Wenn Sie möchten, dass Slack wie eine System-Schnittstelle fungiert, strukturieren Sie Ihre Nachrichten so, dass Entscheidungen offensichtlich sind. Eine Nachrichtenvorlage, die in Teams gut funktioniert:
- Titel
- Schweregrad
- Kontext
- Aktionshinweis
- Links
Eine minimale Vorlage:
Titel: Checkout-Fehlerquote erhöht Schweregrad: warn Kontext: service=checkout env=prod region=us-east Aktionshinweis: Klicken Sie auf „Approve restart", um einen sicheren Neustart auszulösen
Beispiel für eingehenden Webhook-Payload
Eingehende Webhooks akzeptieren JSON-Payloads und können reichhaltige Layouts unter Verwendung von Block Kit enthalten (Nachrichten senden mit eingehenden Webhooks).
{
"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." }
}
]
}
Design von Schaltflächen und Identifikatoren
Schaltflächen müssen in section- oder actions-Blöcken enthalten sein und action_id sowie einen optionalen value beinhalten (Schaltflächen-Element). action_id ist Ihr Routing-Schlüssel. value ist Ihre Payload. Zusammen bilden sie Ihr Ereignisschema.
Meine persönliche Meinung: Wählen Sie action_id-Werte wie stabile API-Endpunkte. Namen wie „approve_restart" altern besser als „button_1".
Verarbeitung von Interaktions-Payloads, response_url und Timing
Slack sendet Interaktions-Payloads an Ihre Request-URL als formular-kodierte Daten mit einem payload-Parameter, der JSON enthält. Die Payload enthält ein type-Feld, das die Quelle definiert, z. B. block_actions für Schaltflächen-Klicks (Verarbeitung von Benutzerinteraktionen, Interaktions-Payloads).
Sie müssen innerhalb von 3 Sekunden HTTP 200 für die Bestätigungsrückantwort zurückgeben (Verarbeitung von Benutzerinteraktionen). Verwenden Sie response_url, um die ursprüngliche Nachricht zu aktualisieren oder im Kanal oder in einem Thread zu antworten, und Slack begrenzt die Nutzung von response_url auf bis zu fünfmal innerhalb von dreißig Minuten (Verarbeitung von Benutzerinteraktionen).
Diese Timing-Beschränkung ist eine Designbeschränkung. Sie zwingt Sie, „bestätigen" von „arbeiten" zu entkoppeln.
Interaktionsmuster, die zu Slack passen
- Schaltflächen in Block Kit für Genehmigungen und Verzweigungen.
- Slash-Befehle für explizite Benutzerabsicht und Parameter.
- Workflow-Schritte für wiederholbare Geschäftsprozesse im Workflow Builder (Workflow-Schritte).
- Shortcuts und Modals, wenn Sie strukturierte Eingaben benötigen, mit
trigger_id-Beschränkungen, die in den Interaktivitäts-Docs beschrieben sind (Verarbeitung von Benutzerinteraktionen).
Go- und Python-Beispiele
Verlagshinweis: Diese können in dedizierte Beispiel-Seiten aufgeteilt werden:
/app-architecture/integration-patterns/slack/go-example/app-architecture/integration-patterns/slack/python-example
Die Beispiele priorisieren eine Sache, die Systeme stabil hält:
- Slack-Signaturen verifizieren
- Innerhalb von drei Sekunden bestätigen (ack)
- Aktionen deduplizieren
- Eine interne HTTP-POST-Anfrage auslösen
- Optional Slack unter Verwendung von
response_urlaktualisieren
Go-Beispiel: Warnung senden und Schaltflächen-Genehmigung verarbeiten
Voraussetzungen:
- Slack-App mit aktivierter Interaktivität und konfigurierter Request-URL (Verarbeitung von Benutzerinteraktionen).
- Bot-Token mit
chat:write-Scope (chat:writeScope). - Ein Signing Secret zur Anfrageverifizierung (Verifizierung von Anfragen von Slack).
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 (
// In der Produktion in Redis mit TTL speichern
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") // z. B. :8080
if botToken == "" || signingSecret == "" || channelID == "" || internalURL == "" || listenAddr == "" {
log.Fatal("fehlende Umgebungsvariablen SLACK_BOT_TOKEN SLACK_SIGNING_SECRET SLACK_CHANNEL_ID INTERNAL_API_URL LISTEN_ADDR")
}
api := slack.New(botToken)
// Eine Warnnachricht mit einer Genehmigungsschaltfläche senden.
// Schaltflächen sind interaktive Block Kit-Elemente mit action_id und value.
// Siehe Slack Block Kit Schaltflächen-Element-Docs.
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 fehlgeschlagen: %v", err)
}
log.Printf("Warnnachricht message_ts=%s gesendet", ts)
// Interaktivitäts-Endpoint
http.HandleFunc("/slack/actions", func(w http.ResponseWriter, r *http.Request) {
rawBody, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Lesen des Body fehlgeschlagen", http.StatusBadRequest)
return
}
r.Body.Close()
// Slack-Anfragesignatur auf dem rohen Body und Timestamp verifizieren.
// Siehe Slack-Docs zur Verifizierung von Anfragen.
if !verifySlackRequest(r.Header, rawBody, signingSecret) {
http.Error(w, "ungültige Signatur", http.StatusUnauthorized)
return
}
// Slack sendet application/x-www-form-urlencoded mit payload=JSON
vals, err := url.ParseQuery(string(rawBody))
if err != nil {
http.Error(w, "schleifer Formular-Body", http.StatusBadRequest)
return
}
payloadStr := vals.Get("payload")
if payloadStr == "" {
http.Error(w, "fehlende Payload", http.StatusBadRequest)
return
}
var p BlockActionPayload
if err := json.Unmarshal([]byte(payloadStr), &p); err != nil {
http.Error(w, "schlechte Payload JSON", http.StatusBadRequest)
return
}
// Innerhalb von 3 Sekunden bestätigen. Echte Arbeit asynchron erledigen und response_url für Updates verwenden.
// Siehe Slack-Interaktivitäts-Docs zur Bestätigungstiming.
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
}
// Genehmigungen deduplizieren
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("interne Aktion fehlgeschlagen: %v", err)
_ = replyViaResponseURL(p.ResponseURL, "Aktion fehlgeschlagen, Protokolle prüfen")
return
}
_ = replyViaResponseURL(p.ResponseURL, "Genehmigung erhalten, interne Aktion ausgelöst")
}()
})
log.Printf("lauschen auf %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
}
// Anfragen älter als 5 Minuten ablehnen, um Replay-Risiko zu reduzieren.
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 akzeptiert JSON-Payloads und kann standardmäßig ephemere senden.
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-Beispiel: Warnung senden und Schaltflächen-Genehmigung verarbeiten
Voraussetzungen:
- Slack-App mit aktivierter Interaktivität und konfigurierter Request-URL (Verarbeitung von Benutzerinteraktionen).
- Bot-Token mit
chat:write-Scope (chat:writeScope). - Signing Secret zur Anfrageverifizierung (Verifizierung von Anfragen von Slack).
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__)
# In der Produktion in Redis speichern
_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 erfordert chat:write Scope.
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 empfiehlt, den rohen Body vor dem Parsen zu verifizieren
if not verifier.is_valid_request(raw_body, request.headers):
return make_response("ungültige Signatur", 401)
# Slack sendet application/x-www-form-urlencoded mit einem payload-Feld, das JSON enthält.
payload_str = request.form.get("payload", "")
if not payload_str:
return make_response("fehlende Payload", 400)
payload = json.loads(payload_str)
# Innerhalb von 3 Sekunden bestätigen. Slack-Benutzer sehen Fehler, wenn Sie das nicht tun.
# Siehe Slack-Interaktivitäts-Docs zur Bestätigung.
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", ""), "Genehmigung erhalten, interne Aktion ausgelöst")
except Exception:
reply_via_response_url(payload.get("response_url", ""), "Aktion fehlgeschlagen, Protokolle prüfen")
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")))
Betriebsnotizen: Routing, UX, Sicherheit, Links und SEO
Wann Slack vs. Paging-Tools vs. Discord verwenden
Diese Seite handelt von Mechaniken. Routing ist Strategie. Dennoch ist die Grenze einfach zu beschreiben.
| Kanal | Am besten für | Fehlermodus |
|---|---|---|
| PagerDuty oder äquivalent | Dringender Einfluss auf Benutzer, der sofortige Reaktion erfordert | Menschen schlafen über Slack hinweg |
| Slack | Koordination, Genehmigungen, Workflow-Ausführung | Lärm und Kanal-Ermüdung |
| Discord | Teams, die in Discord leben, leichtgewichtigere Regelkreise | Weniger Unternehmens-Workflow-Struktur |
Verwenden Sie Slack, wenn Sie möchten, dass die Konversation und der Workflow die Schnittstelle sind. Verwenden Sie Paging-Tools, wenn die Warnung nicht optional ist. Wenn Sie das Design der Slack-Interaktion gegen Service-Grenzen und Persistenzentscheidungen abwägen, hilft diese App-Architektur-Übersicht diese Entscheidung im größeren System zu verorten. Für ein tieferes Routing-Modell siehe Design moderner Warnsysteme für Observability-Teams. Für eine Discord-Integration-Alternative siehe Discord-Integration Pattern für Warnungen und Regelkreise.
Hinweise zu Barrierefreiheit und UX
- Legen Sie Warnungen mit hohem Volumen in ihren eigenen Kanal und behalten Sie menschliche Diskussionen in Threads.
- Verwenden Sie Threads für Kontext und Updates pro Vorfall.
response_urlkann im Kanal und in Threads posten, wennthread_tsbereitgestellt wird (Verarbeitung von Benutzerinteraktionen). - Verwenden Sie ephemere Antworten bei der Bestätigung von Benutzeraktionen, um Kanalspam zu vermeiden, aber denken Sie daran, dass die Lieferung von ephemeren Nachrichten nicht garantiert ist und sessionsabhängig ist (chat.postEphemeral).
- Verwenden Sie den Block Kit Builder, um Layouts schnell zu prototypisieren (Block Kit).
- Wenn Sie Bilder hinzufügen, fügen Sie sinnvolle Alt-Texte hinzu, wo unterstützt, und behalten Sie einen Plain-Text-Fallback im obersten Textfeld.
Sicherheits-Checkliste
- Jede eingehende Slack-Anfrage unter Verwendung von Signing-Secret-Headern und dem rohen Body verifizieren (Verifizierung von Anfragen von Slack).
- Anfragen mit Zeitstempeln, die älter als fünf Minuten sind, ablehnen, um das Replay-Risiko zu reduzieren (Verifizierung von Anfragen von Slack).
- Tokens und Webhook-URLs in einem Secret-Manager speichern, niemals in Git.
- Least-Privilege OAuth-Scopes verwenden und Geheimnisse rotieren, wenn sich Rollen ändern (Scopes Referenz).
- Die interne Aktions-API separat authentifizieren und autorisieren, behandeln Sie Slack nicht als Authentifizierungsgrenze.
- Genehmigungen idempotent und dedupliziert machen.
- Genehmigungen in einer auditfreundlichen Weise protokollieren, einschließlich Team, Kanal, Nachricht-Zeitstempel, Benutzer und Aktion.
Fazit
Slack ist am besten, wenn Sie es als Systemgrenze und nicht als Nachrichten-Speicher behandeln. Eingehende Webhooks decken eine schnelle Warnlieferung ab. Apps plus Interaktivität verwandeln Slack in eine Workflow-Engine und eine Ereignisschnittstelle. Die schwierigen Teile sind die Signaturverifizierung, Timing-Beschränkungen, Deduplizierung und die Wahl, wo Slack in Ihrem Warn-Routing-Modell passt.
Nächste Links: