Modèles d'intégration Slack pour les alertes et les workflows

Slack est une interface utilisateur de flux de travail et une couche de livraison d'alertes.

Sommaire

Les intégrations Slack semblent trompeusement faciles car vous pouvez publier un message en une seule requête HTTP. La partie intéressante commence lorsque vous souhaitez que Slack soit interactif et fiable.

Alertes d’intégration Slack

Cette analyse approfondie traite Slack comme trois surfaces d’intégration distinctes :

  • Un puits de notification pour les alertes unidirectionnelles via des webhooks entrants.
  • Un moteur de flux de travail via le Workflow Builder et des étapes de flux de travail personnalisées.
  • Une interface d’événements via les boutons Block Kit, les commandes slash et les charges utiles d’action.

Cette page décrit comment les systèmes franchissent la frontière vers une interface utilisateur partagée qui peut également émettre des événements vers votre architecture, et non sur la philosophie des alertes. Pour la stratégie et le routage des alertes, consultez Conception de systèmes d’alerte modernes pour les équipes d’observabilité.

Lecture connexe :

Cadrage canonique et placement dans les modèles d’intégration

Slack n’est pas seulement l’endroit où les alertes vont mourir. Bien utilisé, Slack devient une interface système où les messages sont des artefacts avec état et où les interactions utilisateur sont des événements.

Cette page est placée de manière canonique sous /app-architecture/integration-patterns/slack/ car la question principale n’est pas “devons-nous alerter” mais “quel est le contrat entre notre système et Slack”.

Si votre solution nécessite l’un des éléments suivants, vous êtes dans le domaine des modèles d’intégration, et non dans celui de la simple notification :

  • Une boucle de décision, où une approbation humaine déclenche une action.
  • Un flux de travail, où Slack collecte du contexte et déclenche des étapes.
  • Une boucle d’événements, où Slack émet des actions auxquelles votre système s’abonne.

La plateforme Slack soutient intentionnellement à la fois la messagerie unidirectionnelle et l’interaction bidirectionnelle via des URL de demande et des charges utiles d’interaction. Les webhooks entrants sont une méthode de premier ordre pour publier des charges utiles JSON, y compris les éléments de construction Block Kit, vers un canal (Envoi de messages à l’aide de webhooks entrants). L’interactivité est renvoyée à votre application sous forme de requêtes HTTP POST vers une URL de demande configurée, et ces charges utiles sont encodées en formulaire avec un champ de charge utile JSON (Gestion des interactions utilisateur).

Slack en tant que puits de notification

Les webhooks entrants sont le chemin le plus rapide vers la valeur pour les alertes et les mises à jour de statut. Un webhook est une URL unique liée à une installation d’application, et vous POSTEZ un message JSON vers celui-ci (Envoi de messages à l’aide de webhooks entrants).

Opinion : les webhooks sont un excellent choix par défaut lorsque vous souhaitez des messages “envoyer et oublier” et que vous n’avez pas besoin que Slack soit une surface de contrôle. Les webhooks sont également un excellent moyen de découpler votre onboarding de votre architecture d’application finale.

Slack en tant que moteur de flux de travail

Le Workflow Builder existe car le chat est l’endroit où le travail se fait réellement. Les flux de travail peuvent être simples ou complexes et peuvent se connecter à des applications (Guide du Workflow Builder).

Les étapes de flux de travail personnalisées vous permettent d’exposer vos systèmes comme des blocs de construction réutilisables à l’intérieur du Workflow Builder (Étapes de flux de travail). C’est une forme d’intégration différente des bots dans les canaux. Cela rapproche votre intégration de “l’outillage dans Slack” plutôt que de “messages de l’extérieur”.

Opinion : si votre organisation pense déjà en termes de flux de travail et d’approbations, les étapes de flux de travail peuvent sembler plus natives que des bots sur mesure.

Slack en tant qu’interface d’événements

Block Kit transforme un message en une surface d’interface utilisateur (Block Kit). Les composants interactifs comme les boutons génèrent des charges utiles d’action, généralement des charges utiles block_actions, qui sont envoyées à votre application lorsqu’un utilisateur clique (charge utile block_actions).

Les boutons ont des identifiants explicites action_id et une valeur optionnelle, et doivent être hébergés à l’intérieur de blocs section ou actions (Élément de bouton). Lorsque vous concevez un message avec un bouton, vous concevez une source d’événements.

C’est là que des sujets FAQ comme la vérification des demandes, les scopes requis et les déclencheurs internes sûrs deviennent le centre de la conception.

Modèles d’architecture qui passent à l’échelle

Flux de webhook pour l’alerte unidirectionnelle

[service] -> [formatteur d'alerte] -> [webhook entrant Slack] -> [canal]

Les webhooks entrants acceptent des charges utiles JSON et prennent en charge les mises en page Block Kit (Envoi de messages à l’aide de webhooks entrants).

Lorsque la FAQ demande la méthode la plus rapide pour envoyer des alertes, c’est généralement celle-ci.

Flux courtier avec une file d’attente pour la fiabilité et la contre-pression

[services] -> [sujet de file d'attente] -> [expéditeur Slack] -> [API Slack]
                     |                 |
                     |                 +-> [gestionnaire de limite de débit]
                     +-> [file d'attente des lettres mortes]

Les limites de débit de Slack s’appliquent aux API basées sur HTTP, y compris les webhooks entrants, et Slack renvoie un HTTP 429 avec un en-tête Retry-After lorsque vous dépassez les limites (Limites de débit).

Opinion : si vous publiez des alertes directement depuis chaque service, le premier incident se transforme en une attaque de déni de service distribué contre votre propre intégration Slack. Un expéditeur derrière une file d’attente tend à être une architecture plus calme.

Modèle d’automatisation de flux de travail avec approbations

[alerte] -> [message Slack avec bouton] -> [clic sur le bouton]
   -> [charge utile d'action] -> [gestionnaire d'approbation] -> [API interne] -> [mise à jour du message]

L’interactivité Slack nécessite de configurer une URL de demande et d’activer l’interactivité. Slack envoie des charges utiles d’interaction sous forme application/x-www-form-urlencoded avec un paramètre payload contenant du JSON, et vous devez répondre avec un HTTP 200 dans les 3 secondes (Gestion des interactions utilisateur).

C’est le modèle derrière l’élément de la FAQ concernant le déclenchement d’actions internes en toute sécurité.

Diagramme de flux d’interaction Slack

Diagramme d’interaction Slack

Webhook vs application et la mécanique d’implémentation

Bibliothèques recommandées

Go :

Python :

Approche webhook vs bot d’application

Une comparaison pratique :

Capacité Webhook entrant Application Slack avec jeton de bot
Publier des messages Oui Oui
Publier des mises en page Block Kit Oui Oui
Recevoir les clics de boutons Seulement si lié à une application avec interactivité Oui
Commandes slash Non Oui
Étapes de flux de travail Non Oui
Surface de sécurité Secret de l’URL du webhook Jetons OAuth plus secret de signature
Meilleur ajustement Alertes unidirectionnelles Flux de travail, approbations, UI interactive

Slack prend explicitement en charge les mises en page Block Kit avec des webhooks entrants (Envoi de messages à l’aide de webhooks entrants). L’interactivité est configurée par application et livrée à une URL de demande (Gestion des interactions utilisateur).

Opinion : les webhooks sont un excellent premier jalon, mais dès que vous voulez que Slack soit une surface de contrôle, vous construisez une application. Évitez de faire croire le contraire.

Scopes et permissions

Les scopes Slack définissent ce que votre application peut faire. Il existe une référence centrale de scopes et des pages de scope individuelles (Référence des scopes). Pour l’envoi de messages via l’API Web, chat:write est le scope canonique (scope chat:write).

Pour les commandes slash, vous avez généralement besoin du scope commands et d’une URL de demande de commande configurée (les commandes font partie des docs d’interactivité, et chaque commande a sa propre URL de demande) (Gestion des interactions utilisateur).

Note FAQ : la livraison de la charge utile du bouton n’est pas “un scope”, c’est un paramètre d’application. Votre application reçoit des charges utiles lorsque l’Interactivité est activée et que l’URL de demande est définie, mais la publication de mises à jour de messages nécessite généralement chat:write.

Limites de débit et reprises

Les limites de débit de Slack renvoient un HTTP 429 et incluent Retry-After en secondes, et cela s’applique aux API basées sur HTTP, y compris les webhooks entrants (Limites de débit).

En pratique :

  • respecter Retry-After
  • appliquer un backoff avec du jitter pour les 5xx transitoires
  • centraliser la livraison Slack dans un expéditeur lorsque le volume augmente

Idempotence et déduplication

Slack s’attend à une accusé de réception pour les charges utiles d’interaction dans les 3 secondes, sinon les utilisateurs voient une erreur et Slack peut réessayer la livraison selon la fonctionnalité (Gestion des interactions utilisateur). Pour l’API Events, Slack fournit explicitement des en-têtes de métadonnées de reprise x-slack-retry-num (API Events).

Même sans reprises explicites, les doublons se produisent car les utilisateurs double-cliquent et parce que les systèmes distribués réémettent. Si votre bouton déclenche une action interne, traitez les clics comme des événements au moins une fois et dédupliquez.

Une stratégie d’idempotence pratique pour les approbations :

  • clé d’idempotence = team_id + channel_id + message_ts + action_id + user_id
  • stocker la clé dans Redis avec un TTL correspondant à votre fenêtre de flux de travail
  • l’API d’action interne impose également l’idempotence, pas seulement le gestionnaire Slack

Fondamentaux de sécurité et vérification des demandes

Slack signe les demandes vers votre serveur en utilisant votre secret de signature d’application. Slack envoie les en-têtes X-Slack-Signature et X-Slack-Request-Timestamp, et Slack recommande de rejeter les demandes plus anciennes de cinq minutes pour éviter les attaques par rejouabilité (Vérification des demandes provenant de Slack).

Deux pièges qui apparaissent dans les revues de code réelles :

Le SDK Slack Python inclut un utilitaire de vérificateur de signature de demande dans le code et les docs (vérificateur de signature python-slack-sdk).

Conception de messages et d’interactions

Modèle de message d’alerte

Si vous voulez que Slack agisse comme une interface système, structurez vos messages pour que les décisions soient évidentes. Un modèle de message qui fonctionne bien à travers les équipes :

  • titre
  • gravité
  • contexte
  • indice d’action
  • liens

Un modèle minimal :

titre : taux d’erreur de paiement élevé gravité : warn contexte : service=checkout env=prod region=us-east indice d’action : cliquez sur Approver restart pour déclencher un redémarrage sécurisé

Exemple de charge utile de webhook entrant

Les webhooks entrants acceptent des charges utiles JSON et peuvent inclure des mises en page riches en utilisant Block Kit (Envoi de messages à l’aide de webhooks entrants).

{
  "text": "taux d'erreur de paiement élevé",
  "blocks": [
    {
      "type": "header",
      "text": { "type": "plain_text", "text": "taux d'erreur de paiement élevé" }
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": "*gravité*\\nwarn" },
        { "type": "mrkdwn", "text": "*contexte*\\nservice=checkout env=prod region=us-east" }
      ]
    },
    {
      "type": "section",
      "text": { "type": "mrkdwn", "text": "*indice d'action*\\nCliquez sur Approver pour déclencher un redémarrage sécurisé." }
    }
  ]
}

Conception de boutons et d’identifiants

Les boutons doivent être à l’intérieur de blocs section ou actions et inclure action_id et une valeur optionnelle (Élément de bouton). action_id est votre clé de routage. value est votre charge utile. Ensemble, ils constituent votre schéma d’événements.

Opinion : choisissez des valeurs action_id comme des points de terminaison d’API stables. Des noms comme “approve_restart” vieillissent mieux que “button_1”.

Gestion des charges utiles d’interaction, response_url et temporisation

Slack envoie des charges utiles d’interaction à votre URL de demande sous forme de données encodées en formulaire avec un paramètre payload contenant du JSON. La charge utile comprend un champ type définissant la source, tel que block_actions pour les clics de bouton (Gestion des interactions utilisateur, Charges utiles d’interaction).

Vous devez renvoyer un HTTP 200 dans les 3 secondes pour la réponse d’accusé de réception (Gestion des interactions utilisateur). Utilisez response_url pour mettre à jour le message d’origine ou répondre dans le canal ou dans un fil, et Slack limite l’utilisation de response_url à cinq fois dans les trente minutes (Gestion des interactions utilisateur).

Cette contrainte de temporisation est une contrainte de conception. Elle vous force à découpler “accuser réception” de “faire le travail”.

Modèles d’interaction adaptés à Slack

  • Boutons dans Block Kit pour les approbations et la bifurcation.
  • Commandes slash pour l’intention utilisateur explicite et les paramètres.
  • Étapes de flux de travail pour des processus métier répétables dans le Workflow Builder (Étapes de flux de travail).
  • Raccourcis et modaux lorsque vous avez besoin d’une entrée structurée, avec des contraintes trigger_id décrites dans les docs d’interactivité (Gestion des interactions utilisateur).

Exemples Go et Python

Note de l’éditeur : ceux-ci peuvent être divisés en pages d’exemple dédiées :

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

Les exemples privilégient une chose qui maintient les systèmes stables :

  • vérifier les signatures Slack
  • accuser réception dans les trois secondes
  • dédupliquer les actions
  • déclencher une requête HTTP POST interne
  • mettre à jour Slack optionnellement en utilisant response_url

Exemple Go : envoyer une alerte et gérer l’approbation de bouton

Prérequis :

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 (
  // En production, stockez ceci dans Redis avec un 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("variables d'environnement manquantes SLACK_BOT_TOKEN SLACK_SIGNING_SECRET SLACK_CHANNEL_ID INTERNAL_API_URL LISTEN_ADDR")
  }

  api := slack.New(botToken)

  // Envoyer un message d'alerte avec un bouton d'approbation.
  // Les boutons sont des éléments Block Kit interactifs avec action_id et value.
  // Voir les docs d'élément de bouton Slack Block Kit.
  blocks := slack.Blocks{
    BlockSet: []slack.Block{
      slack.NewHeaderBlock(slack.NewTextBlockObject("plain_text", "taux d'erreur de paiement élevé", false, false)),
      slack.NewSectionBlock(
        slack.NewTextBlockObject("mrkdwn", "*gravité*\\nwarn\\n*contexte*\\nservice=checkout env=prod", false, false),
        nil,
        nil,
      ),
      slack.NewActionBlock(
        "actions_1",
        slack.NewButtonBlockElement("approve_restart", "restart", slack.NewTextBlockObject("plain_text", "Approuver le redémarrage", false, false)),
      ),
    },
  }

  _, ts, err := api.PostMessage(channelID, slack.MsgOptionBlocks(blocks.BlockSet...))
  if err != nil {
    log.Fatalf("PostMessage a échoué : %v", err)
  }
  log.Printf("message d'alerte publié message_ts=%s", ts)

  // Point de terminaison d'interactivité
  http.HandleFunc("/slack/actions", func(w http.ResponseWriter, r *http.Request) {
    rawBody, err := io.ReadAll(r.Body)
    if err != nil {
      http.Error(w, "échec de la lecture du corps", http.StatusBadRequest)
      return
    }
    r.Body.Close()

    // Vérifier la signature de la demande Slack sur le corps brut et le timestamp.
    // Voir les docs de Slack sur la vérification des demandes.
    if !verifySlackRequest(r.Header, rawBody, signingSecret) {
      http.Error(w, "signature invalide", http.StatusUnauthorized)
      return
    }

    // Slack envoie application/x-www-form-urlencoded avec payload=JSON
    vals, err := url.ParseQuery(string(rawBody))
    if err != nil {
      http.Error(w, "mauvais corps de formulaire", http.StatusBadRequest)
      return
    }
    payloadStr := vals.Get("payload")
    if payloadStr == "" {
      http.Error(w, "payload manquant", http.StatusBadRequest)
      return
    }

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

    // Accuser réception dans 3 secondes. Faites le vrai travail de manière asynchrone et utilisez response_url pour les mises à jour.
    // Voir les docs d'interactivité de Slack sur la temporisation d'accusé de réception.
    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
      }

      // Dédupliquer les approbations
      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("action interne échouée : %v", err)
        _ = replyViaResponseURL(p.ResponseURL, "action échouée, vérifiez les logs")
        return
      }

      _ = replyViaResponseURL(p.ResponseURL, "approbation reçue, action interne déclenchée")
    }()
  })

  log.Printf("écoute sur %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
  }

  // Rejeter les demandes plus anciennes de 5 minutes pour réduire le risque de rejouabilité.
  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 accepte les charges utiles JSON et peut poster éphémère par défaut.
  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
}

Exemple Python : envoyer une alerte et gérer l’approbation de bouton

Prérequis :

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

# En production, stockez ces éléments dans 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": "taux d'erreur de paiement élevé"}},
        {"type": "section", "text": {"type": "mrkdwn", "text": "*gravité*\\nwarn\\n*contexte*\\nservice=checkout env=prod"}},
        {
            "type": "actions",
            "block_id": "actions_1",
            "elements": [
                {
                    "type": "button",
                    "text": {"type": "plain_text", "text": "Approuver le redémarrage"},
                    "action_id": "approve_restart",
                    "value": "restart"
                }
            ]
        }
    ]
    # chat.postMessage nécessite le scope chat:write.
    client.chat_postMessage(channel=SLACK_CHANNEL_ID, text="taux d'erreur de paiement élevé", blocks=blocks)

@app.post("/slack/actions")
def slack_actions():
    raw_body = request.get_data()  # Slack recommande de vérifier le corps brut avant l'analyse
    if not verifier.is_valid_request(raw_body, request.headers):
        return make_response("signature invalide", 401)

    # Slack envoie application/x-www-form-urlencoded avec un champ payload contenant du JSON.
    payload_str = request.form.get("payload", "")
    if not payload_str:
        return make_response("payload manquant", 400)
    payload = json.loads(payload_str)

    # Accuser réception dans 3 secondes. L'utilisateur Slack voit des erreurs si vous ne le faites pas.
    # Voir les docs d'interactivité de Slack sur l'accusé de réception.
    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", ""), "approbation reçue, action interne déclenchée")
        except Exception:
            reply_via_response_url(payload.get("response_url", ""), "action échouée, vérifiez les 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")))

Notes Ops : routage, UX, sécurité, liens et SEO

Quand utiliser Slack vs outils de page vs Discord

Cette page concerne la mécanique. Le routage est une stratégie. Pourtant, la frontière est facile à décrire.

Canal Meilleur pour Mode de défaillance
PagerDuty ou équivalent Impact utilisateur urgent nécessitant une réponse immédiate Les gens dorment pendant Slack
Slack Coordination, approbations, exécution de flux de travail Bruit et fatigue de canal
Discord Équipes qui vivent dans Discord, boucles de contrôle plus légères Moins de structure de flux de travail d’entreprise

Utilisez Slack lorsque vous voulez que la conversation et le flux de travail soient l’interface. Utilisez les outils de page lorsque l’alerte n’est pas optionnelle. Si vous équilibrez la conception d’interaction Slack contre les limites de service et les choix de persistance, cet aperçu de l’architecture d’application aide à placer cette décision dans le système plus large. Pour un modèle de routage plus profond, consultez Conception de systèmes d’alerte modernes pour les équipes d’observabilité. Pour une alternative d’intégration Discord, consultez Modèle d’intégration Discord pour les alertes et les boucles de contrôle.

Notes d’accessibilité et d’UX

  • Mettez les alertes à fort volume dans leur propre canal et gardez la discussion humaine dans les fils.
  • Utilisez les fils pour le contexte et les mises à jour par incident. response_url peut poster dans le canal et dans les fils lorsque thread_ts est fourni (Gestion des interactions utilisateur).
  • Utilisez des réponses éphémères lors de l’accusé de réception des actions utilisateur pour éviter le spam de canal, mais rappelez-vous que la livraison éphémère n’est pas garantie et dépend de la session (chat.postEphemeral).
  • Utilisez Block Kit Builder pour prototyper des mises en page rapidement (Block Kit).
  • Si vous ajoutez des images, incluez un texte alternatif significatif là où c’est supporté et conservez une chute de texte en clair dans le champ text de niveau supérieur.

Checklist de sécurité

  • Vérifiez chaque demande Slack entrante en utilisant les en-têtes de secret de signature et le corps brut (Vérification des demandes provenant de Slack).
  • Rejetez les demandes avec des timestamps plus anciens de cinq minutes pour réduire le risque de rejouabilité (Vérification des demandes provenant de Slack).
  • Gardez les jetons et les URLs de webhook dans un gestionnaire de secrets, jamais dans git.
  • Utilisez des scopes OAuth de privilège minimum et faites tourner les secrets lorsque les gens changent de rôle (Référence des scopes).
  • Authentifiez et autorisez l’API d’action interne séparément, ne traitez pas Slack comme une frontière d’authentification.
  • Rendre les approbations idempotentes et dédupliquées.
  • Journalisez les approbations d’une manière conviviale à l’audit, y compris l’équipe, le canal, le timestamp du message, l’utilisateur et l’action.

Conclusion

Slack est à son meilleur lorsque vous le traitez comme une frontière système, pas comme un puits de messages. Les webhooks entrants couvrent la livraison rapide d’alertes. Les applications plus l’interactivité transforment Slack en un moteur de flux de travail et une interface d’événements. Les parties difficiles sont la vérification de signature, les contraintes de temporisation, la déduplication et le choix de l’endroit où Slack s’insère dans votre modèle de routage d’alerte.

Liens suivants :