Modelli di integrazione Slack per avvisi e flussi di lavoro

Slack è un'interfaccia utente per il flusso di lavoro e un livello di consegna degli alert.

Indice

Le integrazioni Slack sembrano ingannevolmente semplici perché è possibile pubblicare un messaggio in una sola chiamata HTTP. La parte interessante inizia quando si desidera che Slack sia interattivo e affidabile.

Alerts di Integrazione Slack

Questa analisi approfondita tratta Slack come tre diverse superfici di integrazione:

  • Sink di notifica per alert unidirezionali tramite webhook in entrata.
  • Motore di flussi di lavoro tramite Workflow Builder e passaggi personalizzati.
  • Interfaccia di eventi tramite pulsanti Block Kit, comandi slash e payload di azione.

Questa pagina descrive come i sistemi attraversano il confine verso un’interfaccia utente condivisa che può anche emettere eventi indietro verso la tua architettura, non sulla filosofia degli alert. Per la strategia e il routing degli alert, consulta Progettazione dei Sistemi di Alert Moderni per i Team di Osservabilità.

Lettura correlata:

Inquadramento canonico e posizionamento nei pattern di integrazione

Slack non è solo il luogo dove gli alert vanno a morire. Se usato bene, Slack diventa un’interfaccia di sistema in cui i messaggi sono artefatti con stato e le interazioni degli utenti sono eventi.

Questa pagina è posizionata canonicamente sotto /app-architecture/integration-patterns/slack/ perché la domanda principale non è “dobbiamo inviare alert” ma “qual è il contratto tra il nostro sistema e Slack”.

Se la tua soluzione richiede uno dei seguenti, sei nel territorio dei pattern di integrazione, non in quello delle semplici notifiche:

  • Un ciclo decisionale, dove un’approvazione umana abilita un’azione.
  • Un flusso di lavoro, dove Slack raccoglie il contesto e attiva passaggi.
  • Un ciclo di eventi, dove Slack emette azioni a cui il tuo sistema si abbona.

La piattaforma Slack supporta intenzionalmente sia la messaggistica unidirezionale che l’interazione bidirezionale tramite URL di richiesta e payload di interazione. I webhook in entrata sono un metodo di primo piano per inviare payload JSON, inclusi i componenti Block Kit, a un canale (Invio di messaggi utilizzando webhook in entrata). L’interattività viene restituita alla tua app come richieste HTTP POST a un URL di richiesta configurato, e quei payload sono codificati come form con un campo payload JSON (Gestione delle interazioni utente).

Slack come sink di notifica

I webhook in entrata sono la via più rapida per ottenere valore per gli alert e gli aggiornamenti di stato. Un webhook è un URL univoco legato a un’installazione dell’app, e vi invii un messaggio JSON (Invio di messaggi utilizzando webhook in entrata).

Opinione personale: i webhook sono un predefinito eccellente quando desideri messaggi “invia e dimentica” e non hai bisogno che Slack sia una superficie di controllo. I webhook sono anche un ottimo modo per disaccoppiare il tuo onboarding dalla tua architettura dell’app finale.

Slack come motore di flussi di lavoro

Workflow Builder esiste perché la chat è dove il lavoro avviene realmente. I flussi di lavoro possono essere semplici o complessi e possono connettersi alle app (Guida a Workflow Builder).

I passaggi personalizzati dei flussi di lavoro ti permettono di esporre i tuoi sistemi come blocchi componibili riutilizzabili all’interno di Workflow Builder (Passaggi dei flussi di lavoro). Questa è una forma di integrazione diversa rispetto ai bot nei canali. Sposta la tua integrazione più vicino al “strumentario all’interno di Slack” che a “messaggi dall’esterno”.

Opinione personale: se la tua organizzazione pensa già in termini di flussi di lavoro e approvazioni, i passaggi dei flussi di lavoro possono sembrare più nativi rispetto ai bot su misura.

Slack come interfaccia di eventi

Block Kit trasforma un messaggio in una superficie UI (Block Kit). I componenti interattivi come i pulsanti generano payload di azione, tipicamente payload block_actions, che vengono inviati alla tua app quando un utente fa clic (payload block_actions).

I pulsanti hanno identificatori espliciti action_id e un valore opzionale, e devono essere ospitati all’interno di blocchi section o actions (Elemento pulsante). Quando progetti un messaggio con un pulsante, stai progettando una fonte di eventi.

Qui è dove gli argomenti FAQ come la verifica delle richieste, gli ambiti necessari e i trigger interni sicuri diventano il centro del design.

Pattern di architettura scalabili

Flusso webhook per alert unidirezionali

[servizio] -> [formatore alert] -> [webhook in entrata Slack] -> [canale]

I webhook in entrata accettano payload JSON e supportano layout Block Kit (Invio di messaggi utilizzando webhook in entrata).

Quando l’FAQ chiede qual è il modo più veloce per inviare alert, solitamente è questo.

Flusso tramite broker con una coda per affidabilità e backpressure

[servizi] -> [argomento coda] -> [dispatcher Slack] -> [API Slack]
                     |                 |
                     |                 +-> [gestore limiti di frequenza]
                     +-> [coda dei messaggi morti]

I limiti di frequenza di Slack si applicano alle API basate su HTTP, inclusi i webhook in entrata, e Slack restituisce HTTP 429 con un header Retry-After quando superi i limiti (Limiti di frequenza).

Opinione personale: se invii alert direttamente da ogni servizio, il primo incidente si trasforma in un attacco di denial of service distribuito contro la tua stessa integrazione Slack. Un dispatcher dietro una coda tende a essere un’architettura più calma.

Pattern di automazione del flusso di lavoro con approvazioni

[alert] -> [messaggio Slack con pulsante] -> [clic sul pulsante]
   -> [payload di azione] -> [gestore approvazioni] -> [API interna] -> [aggiorna messaggio]

L’interattività di Slack richiede la configurazione di un URL di Richiesta e l’abilitazione dell’Interattività. Slack invia payload di interazione come application/x-www-form-urlencoded con un parametro payload che contiene JSON, e devi rispondere con HTTP 200 entro 3 secondi (Gestione delle interazioni utente).

Questo è il pattern dietro l’articolo FAQ sull’attivazione sicura di azioni interne.

Diagramma del flusso di interazione Slack

Diagramma di interazione Slack

Webhook vs app e la meccanica di implementazione

Librerie consigliate

Go:

Python:

Approccio Webhook vs App Bot

Un confronto pratico:

Capacità Webhook in entrata App Slack con token bot
Pubblicare messaggi
Pubblicare layout Block Kit
Ricevere clic sui pulsanti Solo se legato a un’app con interattività
Comandi Slash No
Passaggi del flusso di lavoro No
Superficie di sicurezza Segretezza dell’URL del webhook Token OAuth più segreto di firma
Miglior adattamento Alert unidirezionali Flussi di lavoro, approvazioni, UI interattiva

Slack supporta esplicitamente i layout Block Kit con i webhook in entrata (Invio di messaggi utilizzando webhook in entrata). L’interattività è configurata per app e consegnata a un URL di Richiesta (Gestione delle interazioni utente).

Opinione personale: i webhook sono un ottimo primo traguardo, ma non appena desideri che Slack sia una superficie di controllo, stai costruendo un’app. Evita di fingere il contrario.

Ambiti e permessi

Gli ambiti Slack definiscono cosa la tua app può fare. Esiste un riferimento centrale sugli ambiti e pagine individuali per ogni ambito (Riferimento sugli ambiti). Per inviare messaggi tramite Web API, chat:write è l’ambito canonico (ambito chat:write).

Per i comandi slash, hai tipicamente bisogno dell’ambito commands e di un URL di richiesta comando configurato (i comandi fanno parte della documentazione sull’interattività e ogni comando ha il proprio URL di Richiesta) (Gestione delle interazioni utente).

Nota FAQ: la consegna del payload del pulsante non è “un ambito”, è un’impostazione dell’app. La tua app riceve i payload quando l’Interattività è abilitata e l’URL di Richiesta è impostato, ma la pubblicazione di aggiornamenti del messaggio richiede generalmente ancora chat:write.

Limiti di frequenza e ritentativi

I limiti di frequenza di Slack restituiscono HTTP 429 e includono Retry-After in secondi, e ciò si applica alle API basate su HTTP inclusi i webhook in entrata (Limiti di frequenza).

In pratica:

  • rispetta Retry-After
  • applica backoff con jitter per errori transitori 5xx
  • centralizza la consegna Slack in un dispatcher quando il volume cresce

Idempotenza e deduplicazione

Slack si aspetta un’accettazione per i payload di interazione entro 3 secondi, altrimenti gli utenti vedono un errore e Slack potrebbe ritentare la consegna in base alla funzionalità (Gestione delle interazioni utente). Per la Events API, Slack fornisce esplicitamente header di metadati di ritentativo x-slack-retry-num (API Eventi).

Anche senza ritentativi espliciti, si verificano duplicati perché gli utenti fanno doppio clic e perché i sistemi distribuiti ritrasmettono. Se il tuo pulsante attiva un’azione interna, tratta i clic come eventi “almeno una volta” e deduplica.

Una strategia pratica di idempotenza per le approvazioni:

  • chiave idempotenza = team_id + channel_id + message_ts + action_id + user_id
  • memorizza la chiave in Redis con TTL corrispondente alla finestra del tuo flusso di lavoro
  • l’API di azione interna applica anche l’idempotenza, non solo il gestore Slack

Fondamenti di sicurezza e verifica delle richieste

Slack firma le richieste al tuo server utilizzando il tuo segreto di firma dell’app. Slack invia gli header X-Slack-Signature e X-Slack-Request-Timestamp, e Slack consiglia di rifiutare le richieste più vecchie di cinque minuti per prevenire attacchi di replay (Verifica delle richieste da Slack).

Due insidie che appaiono nelle revisioni del codice reali:

  • Devi calcolare la firma sul corpo della richiesta grezzo, prima dell’analisi JSON o del decodifica del form (Verifica delle richieste da Slack).
  • Devi accettare i payload interattivi entro 3 secondi, quindi esegui lavori pesanti in modo asincrono e usa response_url per comunicare i risultati (Gestione delle interazioni utente).

Il Python Slack SDK include un’utilità per la verifica della firma delle richieste nel codice e nella documentazione (verificatore di firma python-slack-sdk).

Design del messaggio e dell’interazione

Template del messaggio di alert

Se desideri che Slack agisca come un’interfaccia di sistema, struttura i tuoi messaggi in modo che le decisioni siano evidenti. Un template di messaggio che funziona bene tra i team:

  • titolo
  • gravità
  • contesto
  • suggerimento di azione
  • link

Un template minimo:

titolo: tasso di errore del checkout elevato gravità: warn contesto: service=checkout env=prod region=us-east suggerimento di azione: clicca Approva riavvio per attivare un riavvio sicuro

Esempio di payload del webhook in entrata

I webhook in entrata accettano payload JSON e possono includere layout ricchi utilizzando Block Kit (Invio di messaggi utilizzando webhook in entrata).

{
  "text": "tasso di errore del checkout elevato",
  "blocks": [
    {
      "type": "header",
      "text": { "type": "plain_text", "text": "tasso di errore del checkout elevato" }
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": "*gravità*\\nwarn" },
        { "type": "mrkdwn", "text": "*contesto*\\nservice=checkout env=prod region=us-east" }
      ]
    },
    {
      "type": "section",
      "text": { "type": "mrkdwn", "text": "*suggerimento di azione*\\nClicca Approva per attivare un riavvio sicuro." }
    }
  ]
}

Progettazione di pulsanti e identificatori

I pulsanti devono essere all’interno di blocchi section o actions e includere action_id e un valore opzionale (Elemento pulsante). action_id è la tua chiave di routing. value è il tuo payload. Insieme, sono il tuo schema di eventi.

Opinione personale: scegli valori action_id come endpoint API stabili. Nomi come “approve_restart” invecchiano meglio di “button_1”.

Gestione del payload di interazione, response_url e tempistica

Slack invia i payload di interazione al tuo URL di Richiesta come dati codificati nel form con un parametro payload contenente JSON. Il payload include un campo type che definisce la fonte, come block_actions per i clic sui pulsanti (Gestione delle interazioni utente, Payload di interazione).

Devi restituire HTTP 200 entro 3 secondi per la risposta di accettazione (Gestione delle interazioni utente). Usa response_url per aggiornare il messaggio originale o rispondere nel canale o in un thread, e Slack limita l’uso di response_url a fino a cinque volte entro trenta minuti (Gestione delle interazioni utente).

Questo vincolo di tempistica è un vincolo di design. Ti costringe a disaccoppiare “accetta” da “esegui il lavoro”.

Pattern di interazione adatti a Slack

  • Pulsanti in Block Kit per approvazioni e ramificazioni.
  • Comandi slash per intento utente esplicito e parametri.
  • Passaggi del flusso di lavoro per processi aziendali ripetibili in Workflow Builder (Passaggi del flusso di lavoro).
  • Scorciatoie e modali quando hai bisogno di input strutturato, con vincoli trigger_id descritti nella documentazione sull’interattività (Gestione delle interazioni utente).

Esempi in Go e Python

Nota dell’autore: questi possono essere suddivisi in pagine di esempio dedicate:

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

Gli esempi danno priorità a una cosa che mantiene i sistemi stabili:

  • verifica le firme Slack
  • accetta entro tre secondi
  • deduplica le azioni
  • attiva un HTTP POST interno
  • opzionalmente aggiorna Slack usando response_url

Esempio Go: invia alert e gestisci approvazione pulsante

Prerequisiti:

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 produzione, memorizza questo in Redis con 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") // es. :8080

  if botToken == "" || signingSecret == "" || channelID == "" || internalURL == "" || listenAddr == "" {
    log.Fatal("variabili d'ambiente mancanti SLACK_BOT_TOKEN SLACK_SIGNING_SECRET SLACK_CHANNEL_ID INTERNAL_API_URL LISTEN_ADDR")
  }

  api := slack.New(botToken)

  // Invia un messaggio di alert con un pulsante di approvazione.
  // I pulsanti sono elementi interattivi Block Kit con action_id e value.
  // Vedi documentazione elemento pulsante Block Kit di Slack.
  blocks := slack.Blocks{
    BlockSet: []slack.Block{
      slack.NewHeaderBlock(slack.NewTextBlockObject("plain_text", "tasso di errore del checkout elevato", false, false)),
      slack.NewSectionBlock(
        slack.NewTextBlockObject("mrkdwn", "*gravità*\\nwarn\\n*contesto*\\nservice=checkout env=prod", false, false),
        nil,
        nil,
      ),
      slack.NewActionBlock(
        "actions_1",
        slack.NewButtonBlockElement("approve_restart", "restart", slack.NewTextBlockObject("plain_text", "Approva riavvio", false, false)),
      ),
    },
  }

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

  // Endpoint di interattività
  http.HandleFunc("/slack/actions", func(w http.ResponseWriter, r *http.Request) {
    rawBody, err := io.ReadAll(r.Body)
    if err != nil {
      http.Error(w, "lettura corpo fallita", http.StatusBadRequest)
      return
    }
    r.Body.Close()

    // Verifica la firma della richiesta Slack sul corpo grezzo e il timestamp.
    // Vedi documentazione Slack sulla verifica delle richieste.
    if !verifySlackRequest(r.Header, rawBody, signingSecret) {
      http.Error(w, "firma non valida", http.StatusUnauthorized)
      return
    }

    // Slack invia application/x-www-form-urlencoded con payload=JSON
    vals, err := url.ParseQuery(string(rawBody))
    if err != nil {
      http.Error(w, "corpo form non valido", http.StatusBadRequest)
      return
    }
    payloadStr := vals.Get("payload")
    if payloadStr == "" {
      http.Error(w, "payload mancante", http.StatusBadRequest)
      return
    }

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

    // Accetta entro 3 secondi. Esegui il lavoro reale in modo asincrono e usa response_url per gli aggiornamenti.
    // Vedi documentazione Slack sulla tempistica dell'accettazione.
    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
      }

      // Deduplica le approvazioni
      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("azione interna fallita: %v", err)
        _ = replyViaResponseURL(p.ResponseURL, "azione fallita, controlla i log")
        return
      }

      _ = replyViaResponseURL(p.ResponseURL, "approvazione ricevuta, azione interna attivata")
    }()
  })

  log.Printf("ascolto su %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
  }

  // Rifiuta richieste più vecchie di 5 minuti per ridurre il rischio di replay.
  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 accetta payload JSON e può pubblicare come ephemeral di default.
  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
}

Esempio Python: invia alert e gestisci approvazione pulsante

Prerequisiti:

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 produzione, memorizza questi in 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": "tasso di errore del checkout elevato"}},
        {"type": "section", "text": {"type": "mrkdwn", "text": "*gravità*\\nwarn\\n*contesto*\\nservice=checkout env=prod"}},
        {
            "type": "actions",
            "block_id": "actions_1",
            "elements": [
                {
                    "type": "button",
                    "text": {"type": "plain_text", "text": "Approva riavvio"},
                    "action_id": "approve_restart",
                    "value": "restart"
                }
            ]
        }
    ]
    # chat.postMessage richiede ambito chat:write.
    client.chat_postMessage(channel=SLACK_CHANNEL_ID, text="tasso di errore del checkout elevato", blocks=blocks)

@app.post("/slack/actions")
def slack_actions():
    raw_body = request.get_data()  # Slack consiglia di verificare il corpo grezzo prima dell'analisi
    if not verifier.is_valid_request(raw_body, request.headers):
        return make_response("firma non valida", 401)

    # Slack invia application/x-www-form-urlencoded con un campo payload contenente JSON.
    payload_str = request.form.get("payload", "")
    if not payload_str:
        return make_response("payload mancante", 400)
    payload = json.loads(payload_str)

    # Accetta entro 3 secondi. Slack mostra errori agli utenti se non lo fai.
    # Vedi documentazione Slack sull'accettazione.
    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", ""), "approvazione ricevuta, azione interna attivata")
        except Exception:
            reply_via_response_url(payload.get("response_url", ""), "azione fallita, controlla i log")

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

Quando usare Slack vs strumenti di paging vs Discord

Questa pagina riguarda la meccanica. Il routing è strategia. Tuttavia, il confine è facile da descrivere.

Canale Migliore per Modalità di fallimento
PagerDuty o equivalente Impatto utente urgente che richiede risposta immediata Le persone dormono attraverso Slack
Slack Coordinamento, approvazioni, esecuzione del flusso di lavoro Rumore e affaticamento del canale
Discord Team che vivono su Discord, cicli di controllo più leggeri Meno struttura dei flussi di lavoro enterprise

Usa Slack quando vuoi che la conversazione e il flusso di lavoro siano l’interfaccia. Usa gli strumenti di paging quando l’alert non è opzionale. Se stai bilanciando il design dell’interazione Slack contro i confini del servizio e le scelte di persistenza, questa panoramica sull’architettura delle app aiuta a inserire quella decisione nel sistema più ampio. Per un modello di routing più approfondito, vedi Progettazione dei Sistemi di Alert Moderni per i Team di Osservabilità. Per un’alternativa di integrazione Discord, vedi Pattern di Integrazione Discord per Alert e Cicli di Controllo.

Note sull’accessibilità e l’esperienza utente (UX)

  • Inserisci gli alert ad alto volume nel loro canale dedicato e mantieni le discussioni umane nei thread.
  • Usa i thread per il contesto e gli aggiornamenti per incidente. response_url può pubblicare nel canale e nei thread quando viene fornito thread_ts (Gestione delle interazioni utente).
  • Usa risposte effimere quando accetti le azioni dell’utente per evitare spam nel canale, ma ricorda che la consegna effimera non è garantita ed è dipendente dalla sessione (chat.postEphemeral).
  • Usa Block Kit Builder per prototipare layout rapidamente (Block Kit).
  • Se aggiungi immagini, includi testo alternativo significativo dove supportato e mantieni un fallback in testo semplice nel campo text di livello superiore.

Checklist di sicurezza

  • Verifica ogni richiesta Slack in entrata utilizzando gli header del segreto di firma e il corpo grezzo (Verifica delle richieste da Slack).
  • Rifiuta le richieste con timestamp più vecchi di cinque minuti per ridurre il rischio di replay (Verifica delle richieste da Slack).
  • Mantieni token e URL dei webhook in un gestore di segreti, mai in git.
  • Usa ambiti OAuth con privilegi minimi e ruota i segreti quando le persone cambiano ruolo (Riferimento sugli ambiti).
  • Autentica e autorizza l’API di azione interna separatamente, non trattare Slack come un confine di autenticazione.
  • Rendi le approvazioni idempotenti e deduplicate.
  • Registra le approvazioni in modo amichevole per l’audit, includendo team, canale, timestamp del messaggio, utente e azione.

Conclusione

Slack è al suo meglio quando lo tratti come un confine di sistema, non come un sink di messaggi. I webhook in entrata coprono la consegna rapida degli alert. Le App più l’interattività trasformano Slack in un motore di flussi di lavoro e un’interfaccia di eventi. Le parti difficili sono la verifica della firma, i vincoli di tempistica, la deduplicazione e la scelta di dove Slack si inserisce nel tuo modello di routing degli alert.

Link successivi: