Patrones de integración de Slack para alertas y flujos de trabajo

Slack es una interfaz de usuario de flujo de trabajo y una capa de entrega de alertas.

Índice

Las integraciones de Slack parecen engañosamente fáciles porque puedes publicar un mensaje en una sola llamada HTTP. La parte interesante comienza cuando quieres que Slack sea interactivo y fiable.

Patrones de integración de Slack para alertas y flujos de trabajo

Esta inmersión profunda trata a Slack como tres superficies de integración diferentes:

  • Sumidero de notificaciones para alertas unidireccionales a través de webhooks entrantes.
  • Motor de flujos de trabajo a través de Workflow Builder y pasos de flujo de trabajo personalizados.
  • Interfaz de eventos a través de botones de Block Kit, comandos de barra de slash y cargas útiles de acciones.

Esta página describe cómo los sistemas cruzan la frontera hacia una interfaz de usuario compartida que también puede emitir eventos de vuelta a tu arquitectura, no sobre la filosofía de las alertas. Para la estrategia y enrutamiento de alertas, consulta Diseño de sistemas de alerta modernos para equipos de observabilidad.

Lectura relacionada:

Encuadre canónico y colocación en patrones de integración

Slack no es solo el lugar donde van a morir las alertas. Usado correctamente, Slack se convierte en una interfaz de sistema donde los mensajes son artefactos con estado y las interacciones de los usuarios son eventos.

Esta página se coloca canónicamente bajo /app-architecture/integration-patterns/slack/ porque la pregunta principal no es “¿debemos alertar?” sino “¿cuál es el contrato entre nuestro sistema y Slack”.

Si tu solución requiere cualquiera de lo siguiente, estás en territorio de patrones de integración, no en territorio de notificación simple:

  • Un bucle de decisión, donde la aprobación humana controla una acción.
  • Un flujo de trabajo, donde Slack recopila contexto y activa pasos.
  • Un bucle de eventos, donde Slack emite acciones a las que tu sistema se suscribe.

La plataforma de Slack soporta intencionalmente tanto la mensajería unidireccional como la interacción bidireccional a través de URLs de solicitud y cargas útiles de interacción. Los webhooks entrantes son una forma de primera clase para publicar cargas útiles JSON, incluidos los bloques de construcción de Block Kit, en un canal (Envío de mensajes usando webhooks entrantes). La interactividad se entrega a tu aplicación como solicitudes HTTP POST a una URL de solicitud configurada, y esas cargas útiles están codificadas como formulario con un campo de carga útil JSON (Manejo de la interacción del usuario).

Slack como sumidero de notificaciones

Los webhooks entrantes son el camino más rápido para obtener valor en alertas y actualizaciones de estado. Un webhook es una URL única vinculada a la instalación de una aplicación, y haces una solicitud POST de un mensaje JSON a ella (Envío de mensajes usando webhooks entrantes).

Opinión: los webhooks son un excelente valor predeterminado cuando quieres mensajes de “entregar y olvidar” y no necesitas que Slack sea una superficie de control. Los webhooks también son una excelente manera de desacoplar tu incorporación de tu arquitectura de aplicación eventual.

Slack como motor de flujos de trabajo

Workflow Builder existe porque el chat es donde realmente ocurre el trabajo. Los flujos de trabajo pueden ser simples o complejos y pueden conectarse a aplicaciones (Guía de Workflow Builder).

Los pasos de flujo de trabajo personalizados te permiten exponer tus sistemas como bloques de construcción reutilizables dentro de Workflow Builder (Pasos de flujo de trabajo). Esta es una forma de integración diferente a los bots en canales. Mueve tu integración más cerca de “herramientas dentro de Slack” que de “mensajes desde fuera”.

Opinión: si tu organización ya piensa en flujos de trabajo y aprobaciones, los pasos de flujo de trabajo pueden sentirse más nativos que los bots hechos a medida.

Slack como interfaz de eventos

Block Kit convierte un mensaje en una superficie de interfaz de usuario (Block Kit). Los componentes interactivos como los botones generan cargas útiles de acción, típicamente cargas útiles de block_actions, que se envían a tu aplicación cuando un usuario hace clic (carga útil block_actions).

Los botones tienen identificadores explícitos action_id y valor opcional, y deben estar alojados dentro de bloques section o actions (Elemento de botón). Cuando diseñas un mensaje con un botón, estás diseñando una fuente de eventos.

Aquí es donde temas frecuentes como la verificación de solicitudes, los alcances requeridos y los disparadores internos seguros se convierten en el centro del diseño.

Patrones de arquitectura que escalan

Flujo de webhook para alertas unidireccionales

[servicio] -> [formatador de alertas] -> [webhook entrante de Slack] -> [canal]

Los webhooks entrantes aceptan cargas útiles JSON y soportan diseños de Block Kit (Envío de mensajes usando webhooks entrantes).

Cuando las preguntas frecuentes preguntan sobre la forma más rápida de enviar alertas, usualmente es esto.

Flujo con intermediario con una cola para fiabilidad y contrapresión

[servicios] -> [tema de cola] -> [dispachador de Slack] -> [API de Slack]
                     |                 |
                     |                 +-> [gestor de límites de tasa]
                     +-> [cola de letra muerta]

Los límites de tasa de Slack se aplican a las APIs basadas en HTTP, incluidos los webhooks entrantes, y Slack devuelve HTTP 429 con un encabezado Retry-After cuando superas los límites (Límites de tasa).

Opinión: si publicas alertas directamente desde cada servicio, el primer incidente se convierte en un denegación de servicio distribuido contra tu propia integración de Slack. Un despachador detrás de una cola tiende a ser una arquitectura más tranquila.

Patrón de automatización de flujos de trabajo con aprobaciones

[alerta] -> [mensaje de Slack con botón] -> [clic en botón]
   -> [carga útil de acción] -> [gestor de aprobación] -> [API interna] -> [actualizar mensaje]

La interactividad de Slack requiere configurar una URL de Solicitud y habilitar Interactividad. Slack envía cargas útiles de interacción como application/x-www-form-urlencoded con un parámetro payload que contiene JSON, y debes responder con HTTP 200 en menos de 3 segundos (Manejo de la interacción del usuario).

Este es el patrón detrás del ítem de preguntas frecuentes sobre cómo activar acciones internas de forma segura.

Diagrama de flujo de interacción de Slack

Diagrama de interacción de Slack

Webhook vs aplicación y la mecánica de implementación

Bibliotecas recomendadas

Go:

Python:

Enfoque de webhook vs bot de aplicación

Una comparación práctica:

Capacidad Webhook entrante Aplicación de Slack con token de bot
Publicar mensajes
Publicar diseños de Block Kit
Recibir clics de botones Solo si está vinculado a una aplicación con interactividad
Comandos de slash No
Pasos de flujo de trabajo No
Superficie de seguridad Secreto de URL del webhook Tokens OAuth más secreto de firma
Mejor ajuste Alertas unidireccionales Flujos de trabajo, aprobaciones, UI interactiva

Slack soporta explícitamente diseños de Block Kit con webhooks entrantes (Envío de mensajes usando webhooks entrantes). La interactividad se configura por aplicación y se entrega a una URL de Solicitud (Manejo de la interacción del usuario).

Opinión: los webhooks son un gran primer hito, pero en cuanto quieras que Slack sea una superficie de control, estás construyendo una aplicación. Evita fingir lo contrario.

Alcances y permisos

Los alcances de Slack definen lo que tu aplicación puede hacer. Hay una referencia central de alcances y páginas individuales de alcances (Referencia de alcances). Para enviar mensajes a través de la API Web, chat:write es el alcance canónico (alcance chat:write).

Para comandos de slash, típicamente necesitas el alcance commands y una URL de solicitud de comando configurada (los comandos son parte de la documentación de interactividad, y cada comando tiene su propia URL de Solicitud) (Manejo de la interacción del usuario).

Nota de preguntas frecuentes: la entrega de carga útil de botones no es “un alcance”, es una configuración de aplicación. Tu aplicación recibe cargas útiles cuando la Interactividad está habilitada y se establece la URL de Solicitud, pero publicar actualizaciones de mensajes generalmente requiere chat:write.

Límites de tasa y reintentos

Los límites de tasa de Slack devuelven HTTP 429 e incluyen Retry-After en segundos, y esto se aplica a las APIs basadas en HTTP, incluidos los webhooks entrantes (Límites de tasa).

En la práctica:

  • honra Retry-After
  • aplica contrapresión con jitter para 5xx transitorios
  • centraliza la entrega de Slack en un despachador cuando crece el volumen

Idempotencia y deduplicación

Slack espera un acuse de recibo para las cargas útiles de interacción en menos de 3 segundos, de lo contrario los usuarios ven un error y Slack puede reintentar el comportamiento de entrega dependiendo de la funcionalidad (Manejo de la interacción del usuario). Para la API de Eventos, Slack proporciona explícitamente encabezados de metadatos de reintento x-slack-retry-num (API de Eventos).

Aunque no haya reintentos explícitos, se producen duplicados porque los usuarios hacen doble clic y porque los sistemas distribuidos retransmiten. Si tu botón activa una acción interna, trata los clics como eventos de al menos una vez y deduplica.

Una estrategia de idempotencia práctica para aprobaciones:

  • clave de idempotencia = team_id + channel_id + message_ts + action_id + user_id
  • almacena la clave en Redis con TTL que coincida con tu ventana de flujo de trabajo
  • la API de acción interna también hace cumplir la idempotencia, no solo el gestor de Slack

Fundamentos de seguridad y verificación de solicitudes

Slack firma las solicitudes a tu servidor usando tu secreto de firma de aplicación. Slack envía los encabezados X-Slack-Signature y X-Slack-Request-Timestamp, y Slack recomienda rechazar solicitudes con más de cinco minutos de antigüedad para prevenir ataques de repetición (Verificación de solicitudes de Slack).

Dos trampas que aparecen en revisiones de código reales:

  • Debes calcular la firma sobre el cuerpo de la solicitud cruda, antes del análisis JSON o decodificación de formulario (Verificación de solicitudes de Slack).
  • Debes acusar de recibo las cargas útiles interactivas en menos de 3 segundos, así que haz el trabajo pesado de forma asíncrona y usa response_url para comunicar resultados (Manejo de la interacción del usuario).

El SDK de Slack para Python incluye una utilidad de verificador de firma de solicitudes en el código y la documentación (verificador de firma python-slack-sdk).

Diseño de mensajes e interacción

Plantilla de mensaje de alerta

Si quieres que Slack actúe como una interfaz de sistema, estructura tus mensajes para que las decisiones sean obvias. Una plantilla de mensaje que funciona bien entre equipos:

  • título
  • severidad
  • contexto
  • pista de acción
  • enlaces

Una plantilla mínima:

título: tasa de error de checkout elevada severidad: warn contexto: service=checkout env=prod region=us-east pista de acción: haz clic en Aprobar reinicio para activar un reinicio seguro

Ejemplo de carga útil de webhook entrante

Los webhooks entrantes aceptan cargas útiles JSON y pueden incluir diseños ricos usando Block Kit (Envío de mensajes usando webhooks entrantes).

{
  "text": "tasa de error de checkout elevada",
  "blocks": [
    {
      "type": "header",
      "text": { "type": "plain_text", "text": "tasa de error de checkout elevada" }
    },
    {
      "type": "section",
      "fields": [
        { "type": "mrkdwn", "text": "*severidad*\\nwarn" },
        { "type": "mrkdwn", "text": "*contexto*\\nservice=checkout env=prod region=us-east" }
      ]
    },
    {
      "type": "section",
      "text": { "type": "mrkdwn", "text": "*pista de acción*\\nHaz clic en Aprobar para activar un reinicio seguro." }
    }
  ]
}

Diseño de botones e identificadores

Los botones deben estar dentro de bloques section o actions e incluir action_id y valor opcional (Elemento de botón). action_id es tu clave de enrutamiento. value es tu carga útil. Juntos, son tu esquema de eventos.

Opinión: elige valores de action_id como puntos finales de API estables. Nombres como “approve_restart” envejecen mejor que “button_1”.

Manejo de carga útil de interacción, response_url y temporización

Slack envía cargas útiles de interacción a tu URL de Solicitud como datos codificados en formulario con un parámetro payload que contiene JSON. La carga útil incluye un campo type que define la fuente, como block_actions para clics de botón (Manejo de la interacción del usuario, Cargas útiles de interacción).

Debes devolver HTTP 200 en menos de 3 segundos para la respuesta de acuse de recibo (Manejo de la interacción del usuario). Usa response_url para actualizar el mensaje original o responder en el canal o en un hilo, y Slack limita el uso de response_url a hasta cinco veces en treinta minutos (Manejo de la interacción del usuario).

Esta restricción de tiempo es una restricción de diseño. Te obliga a desacoplar “acuse de recibo” de “hacer el trabajo”.

Patrones de interacción que encajan en Slack

  • Botones en Block Kit para aprobaciones y bifurcaciones.
  • Comandos de slash para intención explícita del usuario y parámetros.
  • Pasos de flujo de trabajo para procesos de negocio repetibles en Workflow Builder (Pasos de flujo de trabajo).
  • Atajos y modales cuando necesitas entrada estructurada, con restricciones de trigger_id descritas en la documentación de interactividad (Manejo de la interacción del usuario).

Ejemplos de Go y Python

Nota del editor: estos pueden dividirse en páginas de ejemplo dedicadas:

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

Los ejemplos priorizan una cosa que mantiene los sistemas estables:

  • verificar firmas de Slack
  • acusar de recibo en tres segundos
  • deduplicar acciones
  • activar un POST HTTP interno
  • opcionalmente actualizar Slack usando response_url

Ejemplo de Go para enviar alerta y manejar aprobación de botón

Prerrequisitos:

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 producción, almacena esto en 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") // e.g. :8080

  if botToken == "" || signingSecret == "" || channelID == "" || internalURL == "" || listenAddr == "" {
    log.Fatal("faltan variables de entorno SLACK_BOT_TOKEN SLACK_SIGNING_SECRET SLACK_CHANNEL_ID INTERNAL_API_URL LISTEN_ADDR")
  }

  api := slack.New(botToken)

  // Enviar un mensaje de alerta con un botón de aprobación.
  // Los botones son elementos interactivos de Block Kit con action_id y value.
  // Consulta la documentación de elementos de botón de Slack Block Kit.
  blocks := slack.Blocks{
    BlockSet: []slack.Block{
      slack.NewHeaderBlock(slack.NewTextBlockObject("plain_text", "tasa de error de checkout elevada", false, false)),
      slack.NewSectionBlock(
        slack.NewTextBlockObject("mrkdwn", "*severidad*\\nwarn\\n*contexto*\\nservice=checkout env=prod", false, false),
        nil,
        nil,
      ),
      slack.NewActionBlock(
        "actions_1",
        slack.NewButtonBlockElement("approve_restart", "restart", slack.NewTextBlockObject("plain_text", "Aprobar reinicio", false, false)),
      ),
    },
  }

  _, ts, err := api.PostMessage(channelID, slack.MsgOptionBlocks(blocks.BlockSet...))
  if err != nil {
    log.Fatalf("PostMessage falló: %v", err)
  }
  log.Printf("mensaje de alerta publicado message_ts=%s", ts)

  // Endpoint de interactividad
  http.HandleFunc("/slack/actions", func(w http.ResponseWriter, r *http.Request) {
    rawBody, err := io.ReadAll(r.Body)
    if err != nil {
      http.Error(w, "lectura del cuerpo falló", http.StatusBadRequest)
      return
    }
    r.Body.Close()

    // Verificar la firma de la solicitud de Slack en el cuerpo crudo y la marca de tiempo.
    // Consulta la documentación de verificación de solicitudes de Slack.
    if !verifySlackRequest(r.Header, rawBody, signingSecret) {
      http.Error(w, "firma inválida", http.StatusUnauthorized)
      return
    }

    // Slack envía application/x-www-form-urlencoded con payload=JSON
    vals, err := url.ParseQuery(string(rawBody))
    if err != nil {
      http.Error(w, "cuerpo de formulario incorrecto", http.StatusBadRequest)
      return
    }
    payloadStr := vals.Get("payload")
    if payloadStr == "" {
      http.Error(w, "falta payload", http.StatusBadRequest)
      return
    }

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

    // Acusar de recibo en menos de 3 segundos. Haz el trabajo real de forma asíncrona y usa response_url para actualizaciones.
    // Consulta la documentación de interactividad de Slack sobre temporización de acuse de recibo.
    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
      }

      // Deduplicar aprobaciones
      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("acción interna falló: %v", err)
        _ = replyViaResponseURL(p.ResponseURL, "acción falló, revisa los registros")
        return
      }

      _ = replyViaResponseURL(p.ResponseURL, "aprobación recibida, acción interna activada")
    }()
  })

  log.Printf("escuchando en %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
  }

  // Rechazar solicitudes con más de 5 minutos de antigüedad para reducir el riesgo de repetición.
  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 acepta cargas útiles JSON y puede publicar efímero por defecto.
  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
}

Ejemplo de Python para enviar alerta y manejar aprobación de botón

Prerrequisitos:

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 producción, almacena estos en 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": "tasa de error de checkout elevada"}},
        {"type": "section", "text": {"type": "mrkdwn", "text": "*severidad*\\nwarn\\n*contexto*\\nservice=checkout env=prod"}},
        {
            "type": "actions",
            "block_id": "actions_1",
            "elements": [
                {
                    "type": "button",
                    "text": {"type": "plain_text", "text": "Aprobar reinicio"},
                    "action_id": "approve_restart",
                    "value": "restart"
                }
            ]
        }
    ]
    # chat.postMessage requiere alcance chat:write.
    client.chat_postMessage(channel=SLACK_CHANNEL_ID, text="tasa de error de checkout elevada", blocks=blocks)

@app.post("/slack/actions")
def slack_actions():
    raw_body = request.get_data()  # Slack recomienda verificar el cuerpo crudo antes de analizar
    if not verifier.is_valid_request(raw_body, request.headers):
        return make_response("firma inválida", 401)

    # Slack envía application/x-www-form-urlencoded con un campo payload que contiene JSON.
    payload_str = request.form.get("payload", "")
    if not payload_str:
        return make_response("falta payload", 400)
    payload = json.loads(payload_str)

    # Acusar de recibo en menos de 3 segundos. El usuario de Slack verá errores si no lo haces.
    # Consulta la documentación de interactividad de Slack sobre acuse de recibo.
    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", ""), "aprobación recibida, acción interna activada")
        except Exception:
            reply_via_response_url(payload.get("response_url", ""), "acción falló, revisa los registros")

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

Notas de operaciones: enrutamiento, UX, seguridad, enlaces y SEO

Cuándo usar Slack vs herramientas de llamada vs Discord

Esta página es sobre mecánica. El enrutamiento es estrategia. Aun así, la frontera es fácil de describir.

Canal Mejor para Modo de fallo
PagerDuty o equivalente Impacto urgente en el usuario que requiere respuesta inmediata La gente duerme a través de Slack
Slack Coordinación, aprobaciones, ejecución de flujos de trabajo Ruido y fatiga del canal
Discord Equipos que viven en Discord, bucles de control más ligeros Menos estructura de flujo de trabajo empresarial

Usa Slack cuando quieras que la conversación y el flujo de trabajo sean la interfaz. Usa herramientas de llamada cuando la alerta no sea opcional. Si estás equilibrando el diseño de interacción de Slack contra los límites del servicio y las opciones de persistencia, esta visión general de arquitectura de aplicaciones ayuda a colocar esa decisión en el sistema más grande. Para un modelo de enrutamiento más profundo, consulta Diseño de sistemas de alerta modernos para equipos de observabilidad. Para una alternativa de integración de Discord, consulta Patrón de integración de Discord para alertas y bucles de control.

Notas de accesibilidad y UX

  • Coloca alertas de alto volumen en su propio canal y mantén las discusiones humanas en hilos.
  • Usa hilos para contexto y actualizaciones por incidente. response_url puede publicar en el canal y en hilos cuando se proporciona thread_ts (Manejo de la interacción del usuario).
  • Usa respuestas efímeras al reconocer acciones del usuario para evitar spam en el canal, pero recuerda que la entrega efímera no está garantizada y depende de la sesión (chat.postEphemeral).
  • Usa Block Kit Builder para prototipar diseños rápidamente (Block Kit).
  • Si añades imágenes, incluye texto alternativo significativo donde esté soportado y mantén una copia de seguridad de texto plano en el campo de texto de nivel superior.

Lista de verificación de seguridad

  • Verifica cada solicitud entrante de Slack usando encabezados de secreto de firma y cuerpo crudo (Verificación de solicitudes de Slack).
  • Rechaza solicitudes con marcas de tiempo con más de cinco minutos de antigüedad para reducir el riesgo de repetición (Verificación de solicitudes de Slack).
  • Mantén tokens y URLs de webhook en un gestor de secretos, nunca en git.
  • Usa alcances OAuth de privilegio mínimo y rota secretos cuando las personas cambien de roles (Referencia de alcances).
  • Autentica y autoriza la API de acción interna por separado, no trates a Slack como un límite de autenticación.
  • Haz que las aprobaciones sean idempotentes y deduplicadas.
  • Registra las aprobaciones de una manera amigable para auditorías, incluyendo equipo, canal, marca de tiempo del mensaje, usuario y acción.

Conclusión

Slack está en su mejor momento cuando lo tratas como un límite de sistemas, no como un sumidero de mensajes. Los webhooks entrantes cubren la entrega rápida de alertas. Las aplicaciones más interactividad convierten a Slack en un motor de flujos de trabajo e interfaz de eventos. Las partes difíciles son la verificación de firmas, las restricciones de tiempo, la deduplicación y elegir dónde encaja Slack en tu modelo de enrutamiento de alertas.

Enlaces siguientes: