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.
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.

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:
- Plataformas de chat como interfaces de sistema en sistemas modernos
- Patrón de integración de Discord para alertas y bucles de control
- Diseño de sistemas de alerta modernos para equipos de observabilidad
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

Webhook vs aplicación y la mecánica de implementación
Bibliotecas recomendadas
Go:
- slack-go/slack para estructuras de API Web y Block Kit (repo slack-go/slack, documentación pkg.go.dev)
Python:
- slack_sdk (SDK de Slack para Python) para clientes de API Web, ayudantes de firma e infraestructura de reintentos (documentación SDK de Slack para Python, repo python-slack-sdk)
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 | Sí | Sí |
| Publicar diseños de Block Kit | Sí | Sí |
| Recibir clics de botones | Solo si está vinculado a una aplicación con interactividad | Sí |
| Comandos de slash | No | Sí |
| Pasos de flujo de trabajo | No | Sí |
| 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:
- Aplicación de Slack con Interactividad habilitada y URL de Solicitud configurada (Manejo de la interacción del usuario).
- Token de bot con alcance chat:write (alcance chat:write).
- Un secreto de firma para verificación de solicitudes (Verificación de solicitudes de 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 (
// 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:
- Aplicación de Slack con Interactividad habilitada y URL de Solicitud configurada (Manejo de la interacción del usuario).
- Token de bot con alcance chat:write (alcance chat:write).
- Secreto de firma para verificación de solicitudes (Verificación de solicitudes de 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__)
# 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: