Padrões de Integração do Slack para Alertas e Fluxos de Trabalho
O Slack é uma interface de usuário de fluxo de trabalho e uma camada de entrega de alertas.
As integrações do Slack parecem enganadoramente fáceis porque você pode publicar uma mensagem em uma única chamada HTTP. A parte interessante começa quando você deseja que o Slack seja interativo e confiável.

Esta análise aprofundada trata o Slack como três superfícies de integração diferentes:
- Coletor de notificações para alertas unidirecionais via webhooks de entrada.
- Motor de fluxo de trabalho via Workflow Builder e etapas de fluxo de trabalho personalizadas.
- Interface de eventos via botões do Block Kit, comandos de barra inclinada (slash commands) e cargas de trabalho de ação (action payloads).
Esta página descreve como os sistemas cruzam a fronteira para uma UI compartilhada que também pode emitir eventos de volta para sua arquitetura, não sobre a filosofia de alertas. Para estratégia e roteamento de alertas, veja Design de Sistemas de Alerta Modernos para Equipes de Observabilidade.
Leitura relacionada:
- Plataformas de Chat como Interfaces de Sistema em Sistemas Modernos
- Padrão de Integração do Discord para Alertas e Loops de Controle
- Design de Sistemas de Alerta Modernos para Equipes de Observabilidade
Enquadramento canônico e colocação em padrões de integração
O Slack não é apenas o lugar onde os alertas vão morrer. Usado corretamente, o Slack torna-se uma interface de sistema onde as mensagens são artefatos com estado e as interações dos usuários são eventos.
Esta página está colocada canonicamente sob /app-architecture/integration-patterns/slack/ porque a principal questão não é “devemos alertar”, mas “qual é o contrato entre nosso sistema e o Slack”.
Se sua solução requer qualquer uma das seguintes, você está no território de padrões de integração, não no território de notificação simples:
- Um loop de decisão, onde a aprovação humana controla uma ação.
- Um fluxo de trabalho, onde o Slack coleta contexto e aciona etapas.
- Um loop de eventos, onde o Slack emite ações para as quais seu sistema se inscreve.
A plataforma do Slack suporta intencionalmente tanto mensagens unidirecionais quanto interação bidirecional por meio de URLs de solicitação e cargas de trabalho de interação. Webhooks de entrada são uma maneira de primeira classe para publicar cargas de trabalho JSON, incluindo blocos de construção do Block Kit, em um canal (Enviar mensagens usando webhooks de entrada). A interatividade é entregue de volta ao seu aplicativo como solicitações HTTP POST para uma URL de Solicitação configurada, e essas cargas de trabalho são codificadas como formulário com um campo de carga de trabalho JSON (Lidando com interação do usuário).
Slack como coletor de notificações
Webhooks de entrada são o caminho mais rápido para o valor de alertas e atualizações de status. Um webhook é uma URL única vinculada a uma instalação de aplicativo, e você faz um POST de uma mensagem JSON para ele (Enviar mensagens usando webhooks de entrada).
Opinião: webhooks são um padrão excelente quando você deseja mensagens de entrega-e-esqueça e não precisa que o Slack seja uma superfície de controle. Webhooks também são uma maneira excelente de desacoplar sua integração da sua arquitetura de aplicativo eventual.
Slack como motor de fluxo de trabalho
O Workflow Builder existe porque é no chat que o trabalho realmente acontece. Fluxos de trabalho podem ser simples ou complexos e podem se conectar a aplicativos (Guia do Workflow Builder).
Etapas de fluxo de trabalho personalizadas permitem que você exponha seus sistemas como blocos de construção reutilizáveis dentro do Workflow Builder (Etapas de fluxo de trabalho). Esta é uma forma de integração diferente de bots em canais. Isso move sua integração mais para perto de “ferramentas dentro do Slack” do que “mensagens de fora”.
Opinião: se sua organização já pensa em fluxos de trabalho e aprovações, as etapas de fluxo de trabalho podem parecer mais nativas do que bots personalizados.
Slack como interface de eventos
O Block Kit transforma uma mensagem em uma superfície de UI (Block Kit). Componentes interativos como botões geram cargas de trabalho de ação, tipicamente cargas de trabalho block_actions, que são enviadas ao seu aplicativo quando um usuário clica (carga de trabalho block_actions).
Botões têm identificadores explícitos action_id e value opcionais, e devem estar hospedados dentro de blocos section ou actions (Elemento de botão). Quando você projeta uma mensagem com um botão, está projetando uma fonte de eventos.
É aqui que tópicos de FAQ como verificação de solicitação, escopos necessários e gatilhos internos seguros se tornam o centro do design.
Padrões de arquitetura que escalam
Fluxo de webhook para alertas unidirecionais
[service] -> [formatador de alerta] -> [webhook de entrada do Slack] -> [canal]
Webhooks de entrada aceitam cargas de trabalho JSON e suportam layouts do Block Kit (Enviar mensagens usando webhooks de entrada).
Quando o FAQ pergunta sobre a maneira mais rápida de enviar alertas, geralmente é isso.
Fluxo intermediado com uma fila para confiabilidade e contra-pressão
[services] -> [tópico da fila] -> [dispensador do Slack] -> [API do Slack]
| |
| +-> [tratador de limite de taxa]
+-> [fila de carta morta]
Os limites de taxa do Slack se aplicam a APIs baseadas em HTTP, incluindo webhooks de entrada, e o Slack retorna HTTP 429 com um cabeçalho Retry-After quando você excede os limites (Limites de taxa).
Opinião: se você publicar alertas diretamente de cada serviço, o primeiro incidente se transforma em uma negação de serviço distribuída contra sua própria integração do Slack. Um dispensador atrás de uma fila tende a ser uma arquitetura mais calma.
Padrão de automação de fluxo de trabalho com aprovações
[alerta] -> [mensagem do Slack com botão] -> [clique no botão]
-> [carga de trabalho de ação] -> [tratador de aprovação] -> [API interna] -> [atualizar mensagem]
A interatividade do Slack requer configurar uma URL de Solicitação e ativar a Interatividade. O Slack envia cargas de trabalho de interação como application/x-www-form-urlencoded com um parâmetro payload que contém JSON, e você deve responder com HTTP 200 em menos de 3 segundos (Lidando com interação do usuário).
Este é o padrão por trás do item de FAQ sobre acionar ações internas com segurança.
Diagrama de fluxograma de interação do Slack

Webhook vs aplicativo e a mecânica de implementação
Bibliotecas recomendadas
Go:
- slack-go/slack para estruturas de Web API e Block Kit (repositório slack-go/slack, documentação pkg.go.dev)
Python:
- slack_sdk (SDK do Slack para Python) para clientes Web API, ajudantes de assinatura e infraestrutura de nova tentativa (documentação do SDK do Slack para Python, repositório python-slack-sdk)
Abordagem de webhook vs aplicativo de bot
Uma comparação prática:
| Capacidade | Webhook de entrada | Aplicativo do Slack com token de bot |
|---|---|---|
| Publicar mensagens | Sim | Sim |
| Publicar layouts do Block Kit | Sim | Sim |
| Receber cliques em botões | Apenas se vinculado a um aplicativo com interatividade | Sim |
| Comandos de barra inclinada | Não | Sim |
| Etapas de fluxo de trabalho | Não | Sim |
| Superfície de segurança | Sigilo da URL do webhook | Tokens OAuth mais segredo de assinatura |
| Melhor ajuste | Alertas unidirecionais | Fluxos de trabalho, aprovações, UI interativa |
O Slack suporta explicitamente layouts do Block Kit com webhooks de entrada (Enviar mensagens usando webhooks de entrada). A interatividade é configurada por aplicativo e entregue a uma URL de Solicitação (Lidando com interação do usuário).
Opinião: webhooks são um grande primeiro marco, mas assim que você quiser que o Slack seja uma superfície de controle, você está construindo um aplicativo. Evite fingir o contrário.
Escopos e permissões
Os escopos do Slack definem o que seu aplicativo pode fazer. Existe uma referência central de escopos e páginas de escopo individuais (Referência de escopos). Para enviar mensagens via Web API, chat:write é o escopo canônico (escopo chat:write).
Para comandos de barra inclinada, você normalmente precisa do escopo commands e uma URL de solicitação de comando configurada (comandos são parte da documentação de interatividade, e cada comando tem sua própria URL de Solicitação) (Lidando com interação do usuário).
Nota de FAQ: a entrega de carga de trabalho de botão não é “um escopo”, é uma configuração de aplicativo. Seu aplicativo recebe cargas de trabalho quando a Interatividade é ativada e a URL de Solicitação é definida, mas publicar atualizações de mensagem ainda geralmente requer chat:write.
Limites de taxa e novas tentativas
Os limites de taxa do Slack retornam HTTP 429 e incluem Retry-After em segundos, e isso se aplica a APIs baseadas em HTTP, incluindo webhooks de entrada (Limites de taxa).
Na prática:
- respeite Retry-After
- aplique backoff com jitter para 5xx transitórios
- centralize a entrega do Slack em um dispensador quando o volume crescer
Idempotência e deduplicação
O Slack espera um reconhecimento para cargas de trabalho de interação em 3 segundos, caso contrário os usuários veem um erro e o Slack pode repetir o comportamento de entrega dependendo da funcionalidade (Lidando com interação do usuário). Para a API de Eventos, o Slack fornece explicitamente cabeçalhos de metadados de nova tentativa x-slack-retry-num (API de Eventos).
Mesmo sem novas tentativas explícitas, duplicatas ocorrem porque os usuários clicam duas vezes e porque sistemas distribuídos retransmitem. Se seu botão aciona uma ação interna, trate os cliques como eventos de pelo menos uma vez e desduplica.
Uma estratégia prática de idempotência para aprovações:
- chave de idempotência = team_id + channel_id + message_ts + action_id + user_id
- armazene a chave no Redis com TTL correspondendo à janela do seu fluxo de trabalho
- a API de ação interna também impõe idempotência, não apenas o manipulador do Slack
Fundamentos de segurança e verificação de solicitação
O Slack assina solicitações para seu servidor usando seu segredo de assinatura de aplicativo. O Slack envia cabeçalhos X-Slack-Signature e X-Slack-Request-Timestamp, e o Slack recomenda rejeitar solicitações mais antigas que cinco minutos para prevenir ataques de replay (Verificando solicitações do Slack).
Duas armadilhas que aparecem em revisões de código reais:
- Você deve computar a assinatura sobre o corpo bruto da solicitação, antes da análise JSON ou decodificação de formulário (Verificando solicitações do Slack).
- Você deve reconhecer cargas de trabalho interativas em 3 segundos, então faça trabalhos pesados assincronamente e use response_url para comunicar resultados (Lidando com interação do usuário).
O SDK do Slack para Python inclui uma utilidade de verificador de assinatura de solicitação no código e na documentação (verificador de assinatura python-slack-sdk).
Design de mensagem e interação
Modelo de mensagem de alerta
Se você quiser que o Slack atue como uma interface de sistema, estruture suas mensagens para que as decisões sejam óbvias. Um modelo de mensagem que funciona bem em várias equipes:
- título
- gravidade
- contexto
- dica de ação
- links
Um modelo mínimo:
título: taxa de erro de checkout elevada gravidade: warn contexto: service=checkout env=prod region=us-east dica de ação: clique em Aprovar reinício para acionar um reinício seguro
Exemplo de carga de trabalho de webhook de entrada
Webhooks de entrada aceitam cargas de trabalho JSON e podem incluir layouts ricos usando o Block Kit (Enviar mensagens usando webhooks de entrada).
{
"text": "taxa de erro de checkout elevada",
"blocks": [
{
"type": "header",
"text": { "type": "plain_text", "text": "taxa de erro de checkout elevada" }
},
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*gravidade*\\nwarn" },
{ "type": "mrkdwn", "text": "*contexto*\\nservice=checkout env=prod region=us-east" }
]
},
{
"type": "section",
"text": { "type": "mrkdwn", "text": "*dica de ação*\\nClique em Aprovar para acionar um reinício seguro." }
}
]
}
Projetando botões e identificadores
Botões devem estar dentro de blocos section ou actions e incluir action_id e value opcionais (Elemento de botão). action_id é sua chave de roteamento. value é sua carga de trabalho. Juntos, eles são seu esquema de eventos.
Opinião: escolha valores action_id como endpoints de API estáveis. Nomes como “approve_restart” envelhecem melhor do que “button_1”.
Tratamento de carga de trabalho de interação, response_url e temporização
O Slack envia cargas de trabalho de interação para sua URL de Solicitação como dados codificados em formulário com um parâmetro payload contendo JSON. A carga de trabalho inclui um campo type definindo a fonte, como block_actions para cliques em botão (Lidando com interação do usuário, Cargas de trabalho de interação).
Você deve retornar HTTP 200 em menos de 3 segundos para a resposta de reconhecimento (Lidando com interação do usuário). Use response_url para atualizar a mensagem original ou responder no canal ou em uma thread, e o Slack limita o uso de response_url a até cinco vezes em trinta minutos (Lidando com interação do usuário).
Esta restrição de temporização é uma restrição de design. Isso o força a desacoplar “reconhecer” de “fazer trabalho”.
Padrões de interação que se encaixam no Slack
- Botões no Block Kit para aprovações e ramificações.
- Comandos de barra inclinada para intenção de usuário explícita e parâmetros.
- Etapas de fluxo de trabalho para processos de negócios repetíveis no Workflow Builder (Etapas de fluxo de trabalho).
- Atalhos e modais quando você precisa de entrada estruturada, com restrições de trigger_id descritas na documentação de interatividade (Lidando com interação do usuário).
Exemplos em Go e Python
Nota do editor: estes podem ser divididos em páginas de exemplo dedicadas:
/app-architecture/integration-patterns/slack/go-example/app-architecture/integration-patterns/slack/python-example
Os exemplos priorizam uma coisa que mantém os sistemas estáveis:
- verifique as assinaturas do Slack
- reconheça em três segundos
- desduplica ações
- aciona um POST HTTP interno
- opcionalmente atualiza o Slack usando response_url
Exemplo Go: enviar alerta e lidar com aprovação de botão
Pré-requisitos:
- Aplicativo do Slack com Interatividade ativada e URL de Solicitação configurada (Lidando com interação do usuário).
- Token de bot com escopo chat:write (escopo chat:write).
- Um segredo de assinatura para verificação de solicitação (Verificando solicitações do 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 (
// Em produção, armazene isso no Redis com 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("variáveis de ambiente SLACK_BOT_TOKEN SLACK_SIGNING_SECRET SLACK_CHANNEL_ID INTERNAL_API_URL LISTEN_ADDR ausentes")
}
api := slack.New(botToken)
// Envie uma mensagem de alerta com um botão de aprovação.
// Botões são elementos interativos do Block Kit com action_id e value.
// Veja a documentação do elemento de botão do Slack Block Kit.
blocks := slack.Blocks{
BlockSet: []slack.Block{
slack.NewHeaderBlock(slack.NewTextBlockObject("plain_text", "taxa de erro de checkout elevada", false, false)),
slack.NewSectionBlock(
slack.NewTextBlockObject("mrkdwn", "*gravidade*\\nwarn\\n*contexto*\\nservice=checkout env=prod", false, false),
nil,
nil,
),
slack.NewActionBlock(
"actions_1",
slack.NewButtonBlockElement("approve_restart", "restart", slack.NewTextBlockObject("plain_text", "Aprovar reinício", false, false)),
),
},
}
_, ts, err := api.PostMessage(channelID, slack.MsgOptionBlocks(blocks.BlockSet...))
if err != nil {
log.Fatalf("PostMessage falhou: %v", err)
}
log.Printf("mensagem de alerta publicada message_ts=%s", ts)
// Endpoint de interatividade
http.HandleFunc("/slack/actions", func(w http.ResponseWriter, r *http.Request) {
rawBody, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "falha ao ler corpo", http.StatusBadRequest)
return
}
r.Body.Close()
// Verifique a assinatura da solicitação do Slack no corpo bruto e no timestamp.
// Veja a documentação do Slack sobre verificação de solicitações.
if !verifySlackRequest(r.Header, rawBody, signingSecret) {
http.Error(w, "assinatura inválida", http.StatusUnauthorized)
return
}
// O Slack envia application/x-www-form-urlencoded com payload=JSON
vals, err := url.ParseQuery(string(rawBody))
if err != nil {
http.Error(w, "corpo de formulário inválido", http.StatusBadRequest)
return
}
payloadStr := vals.Get("payload")
if payloadStr == "" {
http.Error(w, "payload ausente", http.StatusBadRequest)
return
}
var p BlockActionPayload
if err := json.Unmarshal([]byte(payloadStr), &p); err != nil {
http.Error(w, "json de payload inválido", http.StatusBadRequest)
return
}
// Reconheça em 3 segundos. Faça o trabalho real de forma assíncrona e use response_url para atualizações.
// Veja a documentação de interatividade do Slack sobre temporização de reconhecimento.
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
}
// Desduplica aprovações
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("ação interna falhou: %v", err)
_ = replyViaResponseURL(p.ResponseURL, "ação falhou, verifique os logs")
return
}
_ = replyViaResponseURL(p.ResponseURL, "aprovação recebida, ação interna acionada")
}()
})
log.Printf("ouvindo em %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
}
// Rejeite solicitações mais antigas que 5 minutos para reduzir o risco de 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 aceita cargas de trabalho JSON e pode postar efêmeros por padrão.
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
}
Exemplo Python: enviar alerta e lidar com aprovação de botão
Pré-requisitos:
- Aplicativo do Slack com Interatividade ativada e URL de Solicitação configurada (Lidando com interação do usuário).
- Token de bot com escopo chat:write (escopo chat:write).
- Segredo de assinatura para verificação de solicitação (Verificando solicitações do 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__)
# Em produção, armazene estes no 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": "taxa de erro de checkout elevada"}},
{"type": "section", "text": {"type": "mrkdwn", "text": "*gravidade*\\nwarn\\n*contexto*\\nservice=checkout env=prod"}},
{
"type": "actions",
"block_id": "actions_1",
"elements": [
{
"type": "button",
"text": {"type": "plain_text", "text": "Aprovar reinício"},
"action_id": "approve_restart",
"value": "restart"
}
]
}
]
# chat.postMessage requer escopo chat:write.
client.chat_postMessage(channel=SLACK_CHANNEL_ID, text="taxa de erro de checkout elevada", blocks=blocks)
@app.post("/slack/actions")
def slack_actions():
raw_body = request.get_data() # O Slack recomenda verificar o corpo bruto antes de analisar
if not verifier.is_valid_request(raw_body, request.headers):
return make_response("assinatura inválida", 401)
# O Slack envia application/x-www-form-urlencoded com um campo payload contendo JSON.
payload_str = request.form.get("payload", "")
if not payload_str:
return make_response("payload ausente", 400)
payload = json.loads(payload_str)
# Reconheça em 3 segundos. O usuário do Slack vê erros se você não o fizer.
# Veja a documentação de interatividade do Slack sobre reconhecimento.
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", ""), "aprovação recebida, ação interna acionada")
except Exception:
reply_via_response_url(payload.get("response_url", ""), "ação falhou, verifique os 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")))
Notas de operações: roteamento, UX, segurança, links e SEO
Quando usar Slack vs ferramentas de chamada vs Discord
Esta página é sobre mecânica. Roteamento é estratégia. Ainda assim, a fronteira é fácil de descrever.
| Canal | Melhor para | Modo de falha |
|---|---|---|
| PagerDuty ou equivalente | Impacto de usuário urgente exigindo resposta imediata | Pessoas dormem durante o Slack |
| Slack | Coordenação, aprovações, execução de fluxo de trabalho | Ruído e fadiga de canal |
| Discord | Equipes que vivem no Discord, loops de controle mais leves | Menos estrutura de fluxo de trabalho empresarial |
Use o Slack quando você quiser que a conversa e o fluxo de trabalho sejam a interface. Use ferramentas de chamada quando o alerta não for opcional. Se você estiver equilibrando o design de interação do Slack contra fronteiras de serviço e escolhas de persistência, esta visão geral de arquitetura de aplicativos ajuda a colocar essa decisão no sistema maior. Para um modelo de roteamento mais profundo, veja Design de Sistemas de Alerta Modernos para Equipes de Observabilidade. Para uma alternativa de integração do Discord, veja Padrão de Integração do Discord para Alertas e Loops de Controle.
Notas de acessibilidade e UX
- Coloque alertas de alto volume em seu próprio canal e mantenha a discussão humana em threads.
- Use threads para contexto e atualizações por incidente. response_url pode postar no canal e em threads quando thread_ts é fornecido (Lidando com interação do usuário).
- Use respostas efêmeras ao reconhecer ações de usuário para evitar spam no canal, mas lembre-se que a entrega efêmera não é garantida e depende da sessão (chat.postEphemeral).
- Use o Block Kit Builder para prototipar layouts rapidamente (Block Kit).
- Se você adicionar imagens, inclua texto alternativo significativo onde suportado e mantenha uma fallback de texto puro no campo de texto de nível superior.
Lista de verificação de segurança
- Verifique cada solicitação de entrada do Slack usando cabeçalhos de segredo de assinatura e corpo bruto (Verificando solicitações do Slack).
- Rejeite solicitações com timestamps mais antigos que cinco minutos para reduzir o risco de replay (Verificando solicitações do Slack).
- Mantenha tokens e URLs de webhook em um gerenciador de segredos, nunca no git.
- Use escopos OAuth de privilégio mínimo e rotacione segredos quando as pessoas mudarem de função (Referência de escopos).
- Autentique e autorize a API de ação interna separadamente, não trate o Slack como uma fronteira de autenticação.
- Torne as aprovações idempotentes e desduplicadas.
- Registre aprovações de uma maneira amigável para auditoria, incluindo equipe, canal, timestamp da mensagem, usuário e ação.
Conclusão
O Slack está no seu melhor quando você o trata como uma fronteira de sistemas, não como um coletor de mensagens. Webhooks de entrada cobrem a entrega rápida de alertas. Aplicativos mais interatividade transformam o Slack em um motor de fluxo de trabalho e interface de eventos. As partes difíceis são verificação de assinatura, restrições de temporização, desduplicação e escolher onde o Slack se encaixa em seu modelo de roteamento de alertas.
Próximos links: