Guide 05

How to build a Slack bot: scopes, Events API, Socket Mode and Block Kit

Slack bots live inside companies, so trust is everything. Request few scopes, verify every request and respond within three seconds.

Guide3 min readUpdated

Create the app from a manifest

At api.slack.com/apps choose Create New App, From a manifest. A manifest keeps configuration in version control:

display_information:
  name: Deploy Helper
  description: Posts deploy status and runs approved rollbacks
features:
  bot_user: { display_name: deploy-helper, always_online: false }
  slash_commands:
    - command: /deploy
      description: Show or trigger deploys
      usage_hint: "status | rollback <service>"
      should_escape: false
oauth_config:
  scopes:
    bot: [app_mentions:read, chat:write, commands]
settings:
  event_subscriptions:
    bot_events: [app_mention]
  interactivity: { is_enabled: true }
  socket_mode_enabled: true

Install the app to your workspace to get a bot token (xoxb-...). Copy the signing secret from Basic Information.

Scopes: ask for less

Scopes define what the token can do. chat:write posts messages; app_mentions:read receives mentions; commands enables slash commands. Avoid broad scopes such as channels:history or users:read.email unless a feature truly needs them: admins review scopes before approving apps, and every scope is a risk if the token leaks.

Receiving events

Events API over HTTP. Slack POSTs events to your Request URL. When you set the URL, Slack sends a url_verification payload: respond with its challenge. Every request must be acknowledged with 200 within 3 seconds; otherwise Slack retries up to three times with X-Slack-Retry-Num and X-Slack-Retry-Reason headers. Deduplicate on event_id.

Socket Mode. Your app opens a WebSocket to Slack using an app-level token (xapp-..., scope connections:write). No public URL is needed, which is ideal for internal tools behind a firewall or for local development. You still acknowledge each envelope.

Verify every HTTP request

Slack signs requests with your signing secret. Compute v0= + HMAC-SHA256 of v0:{X-Slack-Request-Timestamp}:{raw body} and compare with X-Slack-Signature in constant time. Reject timestamps older than five minutes to block replays. Bolt does this for you; the webhook tester has a Slack mode for debugging.

A Bolt app

import bolt from "@slack/bolt";
const { App } = bolt;

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  appToken: process.env.SLACK_APP_TOKEN,   // xapp- token for Socket Mode
  socketMode: true,
});

app.command("/deploy", async ({ command, ack, respond }) => {
  await ack();                                       // acknowledge within 3 s
  const [action, service] = command.text.trim().split(/\s+/);
  if (action === "status") return respond(await deployStatus());
  if (action === "rollback" && service) {
    return respond({
      response_type: "ephemeral",
      blocks: [
        { type: "section", text: { type: "mrkdwn", text: `Roll back *${service}* to the previous release?` } },
        { type: "actions", elements: [
          { type: "button", text: { type: "plain_text", text: "Roll back" }, style: "danger", action_id: "rollback", value: service },
          { type: "button", text: { type: "plain_text", text: "Cancel" }, action_id: "cancel" } ] },
      ],
    });
  }
  return respond("Usage: /deploy status | rollback <service>");
});

app.action("rollback", async ({ ack, body, action, respond }) => {
  await ack();
  if (!(await isOnCall(body.user.id))) return respond("Only the on-call engineer can roll back.");
  await rollback(action.value, { requestedBy: body.user.id });
  await respond(`Rollback of ${action.value} started by <@${body.user.id}>.`);
});

await app.start();

Note the authorization check inside the action handler: anyone who can see a button can click it.

Block Kit

Messages, modals and the App Home tab are built from blocks: sections, actions, inputs, context, dividers. Prototype in the Block Kit Builder. Always include a top-level text fallback for notifications and screen readers.

Rate limits

Web API methods are grouped in tiers, from Tier 1 (about one request per minute) to Tier 4 (100+ per minute). chat.postMessage has a special limit of roughly one message per second per channel, with short bursts allowed. On 429 read the Retry-After header. For bulk notifications, queue and spread them.

Distribution

Internal apps are installed in one workspace. To let other workspaces install yours, enable distribution and implement the OAuth flow, storing one token per workspace (encrypted at rest). Listing in the Slack Marketplace requires a security review, a privacy policy and a support contact.

Check your understanding

1. Which string does Slack sign?
2. What does Socket Mode remove the need for?