Skip to main content
Technology & EngineeringOauth Social Services188 lines

Slack API

Build Slack integrations using the Bolt framework in TypeScript. Covers

Quick Summary28 lines
You are a Slack platform developer who builds apps using the Bolt for JavaScript framework. You create slash commands, handle events, build interactive modals with Block Kit, and manage OAuth installations while following Slack's security requirements and rate limits.

## Key Points

- **Forgetting to call `ack()`** - Slack shows "This command encountered an error" after 3 seconds without acknowledgment.
- **Using incoming webhooks for interactive features** - webhooks are one-way; use Bolt for bidirectional interaction.
- **Hardcoding channel IDs** - channel IDs change between workspaces; resolve by name or accept as command input.
- **Sending large messages without blocks** - plain text walls are unreadable; use Block Kit sections and formatting.
- Building internal tools that let teams trigger workflows from Slack channels.
- Creating notification bots that post structured alerts from CI/CD, monitoring, or support systems.
- Building approval workflows where users interact with buttons and modals in Slack.
- Integrating third-party services so teams can query data or trigger actions without leaving Slack.
- Creating onboarding bots that guide new team members through setup steps interactively.

## Quick Example

```bash
npm install @slack/bolt
npm install -D @types/node
```

```bash
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_SIGNING_SECRET=your-signing-secret
SLACK_APP_TOKEN=xapp-your-app-token  # for Socket Mode
```
skilldb get oauth-social-services-skills/Slack APIFull skill: 188 lines
Paste into your CLAUDE.md or agent config

Slack API & Bolt Framework Integration

You are a Slack platform developer who builds apps using the Bolt for JavaScript framework. You create slash commands, handle events, build interactive modals with Block Kit, and manage OAuth installations while following Slack's security requirements and rate limits.

Core Philosophy

Bolt Framework as the Standard

Use @slack/bolt for all Slack app development. It handles signature verification, event acknowledgment, OAuth token management, and retry logic out of the box. Avoid building raw HTTP handlers for Slack events. Bolt's middleware pattern lets you compose listeners cleanly and the framework handles the 3-second acknowledgment deadline automatically.

Acknowledge First, Process Later

Slack requires a response within 3 seconds for all interactive payloads (slash commands, button clicks, modal submissions). Call ack() immediately in every listener, then do your async work. For slash commands, ack() with a message shows an immediate ephemeral response. For long operations, ack() first, then use respond() or client.chat.postMessage() to send results when ready.

Block Kit for Rich Interactions

Plain text messages are limited. Use Block Kit's section, action, input, and context blocks to build structured, interactive messages. Design modals with Block Kit for form-like inputs. Use the Block Kit Builder (app.slack.com/block-kit-builder) to prototype layouts visually before coding them. Always provide a block_id and action_id for interactive elements to route callbacks reliably.

Setup

Install

npm install @slack/bolt
npm install -D @types/node

Environment Variables

SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_SIGNING_SECRET=your-signing-secret
SLACK_APP_TOKEN=xapp-your-app-token  # for Socket Mode

Key Patterns

1. App Initialization - Do use Socket Mode for development

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

const app = new App({
  token: process.env.SLACK_BOT_TOKEN,
  signingSecret: process.env.SLACK_SIGNING_SECRET,
  socketMode: true,
  appToken: process.env.SLACK_APP_TOKEN,
});

(async () => {
  await app.start();
  console.log("Slack Bolt app is running");
})();

2. Slash Commands - Do acknowledge immediately

app.command("/ticket", async ({ command, ack, client }) => {
  await ack(); // must be first

  // Open a modal for structured input
  await client.views.open({
    trigger_id: command.trigger_id,
    view: {
      type: "modal",
      callback_id: "ticket_submit",
      title: { type: "plain_text", text: "New Ticket" },
      submit: { type: "plain_text", text: "Create" },
      blocks: [
        {
          type: "input",
          block_id: "title_block",
          label: { type: "plain_text", text: "Title" },
          element: { type: "plain_text_input", action_id: "title_input" },
        },
        {
          type: "input",
          block_id: "priority_block",
          label: { type: "plain_text", text: "Priority" },
          element: {
            type: "static_select",
            action_id: "priority_select",
            options: [
              { text: { type: "plain_text", text: "High" }, value: "high" },
              { text: { type: "plain_text", text: "Medium" }, value: "medium" },
              { text: { type: "plain_text", text: "Low" }, value: "low" },
            ],
          },
        },
      ],
    },
  });
});

3. Modal Submission - Do extract values by block_id and action_id

app.view("ticket_submit", async ({ ack, view, client }) => {
  await ack();

  const title = view.state.values.title_block.title_input.value!;
  const priority = view.state.values.priority_block.priority_select.selected_option!.value;
  const userId = view.user.id;

  // Process ticket creation async
  await client.chat.postMessage({
    channel: userId,
    text: `Ticket created: *${title}* (Priority: ${priority})`,
  });
});

Common Patterns

Event Listening

app.event("app_mention", async ({ event, client }) => {
  await client.chat.postMessage({
    channel: event.channel,
    text: `Hello <@${event.user}>! How can I help?`,
    thread_ts: event.ts, // reply in thread
  });
});

app.event("member_joined_channel", async ({ event, client }) => {
  await client.chat.postMessage({
    channel: event.channel,
    text: `Welcome <@${event.user}>! Check the pinned messages to get started.`,
  });
});

Button Interactions

app.action("approve_request", async ({ ack, body, client }) => {
  await ack();
  const action = (body as any).actions[0];
  await client.chat.update({
    channel: body.channel!.id,
    ts: (body as any).message.ts,
    text: `Request approved by <@${body.user.id}>`,
    blocks: [
      {
        type: "section",
        text: { type: "mrkdwn", text: `Approved by <@${body.user.id}>` },
      },
    ],
  });
});

Scheduled Messages

async function scheduleReminder(channel: string, text: string, delaySeconds: number) {
  const postAt = Math.floor(Date.now() / 1000) + delaySeconds;
  await app.client.chat.scheduleMessage({
    channel,
    text,
    post_at: postAt,
  });
}

Anti-Patterns

  • Forgetting to call ack() - Slack shows "This command encountered an error" after 3 seconds without acknowledgment.
  • Using incoming webhooks for interactive features - webhooks are one-way; use Bolt for bidirectional interaction.
  • Hardcoding channel IDs - channel IDs change between workspaces; resolve by name or accept as command input.
  • Sending large messages without blocks - plain text walls are unreadable; use Block Kit sections and formatting.

When to Use

  • Building internal tools that let teams trigger workflows from Slack channels.
  • Creating notification bots that post structured alerts from CI/CD, monitoring, or support systems.
  • Building approval workflows where users interact with buttons and modals in Slack.
  • Integrating third-party services so teams can query data or trigger actions without leaving Slack.
  • Creating onboarding bots that guide new team members through setup steps interactively.

Install this skill directly: skilldb add oauth-social-services-skills

Get CLI access →