Slack
Escopo: JavaScript/Node.js. Guia baseado em @slack/bolt v4.7.1 com Node.js 22. Conceitos fundamentais (webhook, polling, command routing, rate limit): shared/platform/bots.md. Primitivas Slack (tokens, Socket Mode, Block Kit, scopes): shared/platform/bots-advanced.md.
Bolt for JavaScript é o framework oficial da Slack para construir aplicativos Slack. Um objeto App (aplicativo) registra listeners para slash commands, eventos e ações de Block Kit. Toda interação enviada pela Slack exige um ack() (acknowledge, reconhecimento) em até 3 segundos. Sem esse retorno, a Slack exibe um erro ao usuário.
Conceitos fundamentais
| Conceito | O que é |
|---|---|
| App (aplicativo Bolt) | Instância principal do bot; registra listeners e gerencia a conexão com o Slack |
| ack() (reconhecer recebimento) | Função de reconhecimento obrigatória; confirma ao Slack que o evento foi recebido em até 3 segundos |
| say() (dizer no canal) | Envia uma mensagem ao canal onde o evento ocorreu; aceita string ou objeto com blocks |
| respond() (responder via URL) | Responde via response_url; funciona fora do canal original e aceita thread_ts para replies em thread |
| Block Kit (kit de blocos de UI) | Sistema de UI do Slack; mensagens compostas por blocos (section, actions, image) com elementos interativos |
| Socket Mode (modo socket) | Conexão via WebSocket usando um App-Level Token; dispensa URL pública; indicado para desenvolvimento e bots internos |
| Bot Token (token do bot) | Credencial xoxb-... emitida pela Slack para chamadas à API; obtida em OAuth & Permissions |
| Signing Secret (segredo de assinatura) | Chave usada para validar que as requisições HTTP vêm da Slack; obtida em Basic Information |
| App-Level Token (token de nível de aplicativo) | Credencial xapp-... usada exclusivamente no Socket Mode; requer scope connections:write |
Instalação
npm install @slack/bolt
Setup do App
Use process.env para todas as credenciais. O await em app.start() é obrigatório: sem ele, erros de inicialização são silenciados.
❌ Ruim: credenciais hardcoded; sem await no start
import { App } from '@slack/bolt';
const app = new App({
token: 'xoxb-1234-...',
signingSecret: 'abc123',
});
app.start(3000);
✅ Bom: credenciais via env; await no start; port via env
import { App } from '@slack/bolt';
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
});
await app.start(process.env.PORT ?? 3000);
Slash Commands
ack() deve ser chamado antes de qualquer operação assíncrona. A Slack aguarda o reconhecimento em até 3 segundos; operações longas são iniciadas após o ack().
❌ Ruim: sem ack(); destructuring no parâmetro; lógica de negócio no handler; format inline no say()
app.command('/order', async ({ ack, say, command }) => {
const orderId = command.text.trim();
const order = await db.findOrder(orderId);
await say(`Pedido #${order.id}: ${order.status}, Cliente: ${order.customerName}`);
});
✅ Bom: ack() primeiro; destructuring no corpo; orquestrador + helpers abaixo
app.command('/order', async (commandPayload) => {
const { ack, say, command } = commandPayload;
await ack();
const orderId = command.text.trim();
if (!orderId) {
await say('Informe o ID do pedido. Exemplo: /order 12345');
return;
}
const order = await fetchOrder(orderId);
const reply = buildOrderReply(order);
await say(reply);
});
async function fetchOrder(orderId) {
const order = await orderRepository.findById(orderId);
return order;
}
function buildOrderReply(order) {
const reply = `Pedido #${order.id}\nStatus: ${order.status}\nCliente: ${order.customerName}`;
return reply;
}
Events
Use app.event() para reagir a eventos da Events API. Em listeners de mensagem, adicione um guard para event.bot_id: sem ele, o bot responde aos próprios envios e entra em loop.
❌ Ruim: destructuring no parâmetro; sem guard para bot messages; format inline no say()
app.event('app_mention', async ({ event, say }) => {
await say(`Olá, <@${event.user}>! Como posso ajudar?`);
});
app.event('message', async ({ event, say }) => {
await say(`Recebi: ${event.text}`);
});
✅ Bom: destructuring no corpo; guard para bot messages; compute extraído antes do say()
app.event('app_mention', async (eventPayload) => {
const { event, say } = eventPayload;
const mentionReply = buildMentionReply(event.user);
await say(mentionReply);
});
app.event('message', async (eventPayload) => {
const { event, say } = eventPayload;
if (event.bot_id) return;
const echoReply = buildEchoReply(event.text);
await say(echoReply);
});
function buildMentionReply(userId) {
const reply = `Olá, <@${userId}>! Use /order <id> para consultar um pedido.`;
return reply;
}
function buildEchoReply(messageText) {
const reply = `Recebi: ${messageText}`;
return reply;
}
Actions (Block Kit)
Block Kit é a primitiva de UI interativa do Slack. Botões e selects disparam app.action(). O ack() é obrigatório também nas ações: sem ele, a Slack exibe um spinner indefinido no botão.
❌ Ruim: blocks montados inline; action_id como string solta; sem ack() na ação
app.command('/menu', async ({ ack, say }) => {
await ack();
await say({
blocks: [
{ type: 'section', text: { type: 'mrkdwn', text: 'Escolha uma opção:' } },
{
type: 'actions',
elements: [
{ type: 'button', text: { type: 'plain_text', text: 'Ver pedidos' }, action_id: 'view_orders' },
],
},
],
});
});
app.action('view_orders', async ({ say }) => {
await say('Mostrando seus pedidos...');
});
✅ Bom: blocks extraídos; action_id como constante; ack() antes do processamento
const ACTIONS = {
VIEW_ORDERS: 'view_orders',
};
const MENU_BLOCKS = [
{
type: 'section',
text: { type: 'mrkdwn', text: 'Escolha uma opção:' },
},
{
type: 'actions',
elements: [
{
type: 'button',
text: { type: 'plain_text', text: 'Ver pedidos' },
action_id: ACTIONS.VIEW_ORDERS,
},
],
},
];
app.command('/menu', async (commandPayload) => {
const { ack, say } = commandPayload;
await ack();
const menuPayload = { blocks: MENU_BLOCKS };
await say(menuPayload);
});
app.action(ACTIONS.VIEW_ORDERS, async (actionPayload) => {
const { ack, say } = actionPayload;
await ack();
const orders = await fetchUserOrders();
const reply = buildOrdersReply(orders);
await say(reply);
});
async function fetchUserOrders() {
const orders = await orderRepository.findRecent();
return orders;
}
function buildOrdersReply(orders) {
const lines = orders.map((order) => `#${order.id}: ${order.status}`);
const reply = lines.join('\n');
return reply;
}
Socket Mode
Socket Mode elimina a necessidade de URL pública e é recomendado para bots internos e desenvolvimento local. O appToken deve ter o scope connections:write.
❌ Ruim: tokens hardcoded; sem await no start
import { App } from '@slack/bolt';
const app = new App({
token: 'xoxb-...',
socketMode: true,
appToken: 'xapp-...',
});
app.start();
✅ Bom: tokens via env; await no start; sem port (Socket Mode não usa porta)
import { App } from '@slack/bolt';
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
socketMode: true,
appToken: process.env.SLACK_APP_TOKEN,
});
await app.start();
Veja também
- shared/platform/bots-advanced.md (Slack): tokens, Socket Mode, Block Kit, scopes (conceitual)
- shared/platform/bots.md: webhook vs polling, session state, rate limit
Desenvolvido por @thiagocajadev · Fork baseado no repositório pmndrs/docs · Poimandres.