Bot SDK Coming soon

A native TypeScript SDK (@disqua/sdk) is on the roadmap to wrap authentication, WebSocket connection management, retries, and type-safe events. Until it ships, you can already build bots and slash commands today against the REST and webhook APIs — see API Reference and Webhooks. The snippets on this page preview the SDK's intended API surface.

Installation

# npm
npm install @disqua/sdk

# pnpm
pnpm add @disqua/sdk

# yarn
yarn add @disqua/sdk

Requires Node.js 18+ and TypeScript 5.0+. The SDK ships with full type definitions — no @types package needed.

PackageDescriptionVersion
@disqua/sdkMain SDK — client, events, slash commands, Block Kit1.x
@disqua/sdk/expressExpress middleware for slash command endpoints1.x
@disqua/sdk/fastifyFastify plugin for slash command endpoints1.x

Create a bot

First, create a Bot integration in your workspace (Settings → Integrations → Bots → New Bot). You'll get a bot token — treat it like a password.

src/bot.ts — minimal bot
import { DisquaClient } from '@disqua/sdk';

const client = new DisquaClient({
  token: process.env.DISQUA_BOT_TOKEN!,     // bot token from workspace settings
  workspaceId: process.env.DISQUA_WORKSPACE_ID!,
});

// Connect and start listening for events
await client.connect();

console.log('Bot connected and ready!');

// Graceful shutdown
process.on('SIGTERM', () => client.disconnect());

client.connect() establishes a WebSocket connection to wss://api.disqua.com/ws with automatic reconnect and heartbeat management.

Event handlers

Subscribe to workspace events using client.on(). All event payloads are fully typed.

import { DisquaClient, MessageCreatedEvent } from '@disqua/sdk';

const client = new DisquaClient({ token: process.env.DISQUA_BOT_TOKEN!, workspaceId: '...' });

// Respond to messages that mention the bot
client.on('message.created', async (event: MessageCreatedEvent) => {
  const { message, channel, author } = event;

  // Ignore messages from bots to prevent loops
  if (author.isBot) return;

  // React when someone says "hello" to the bot
  if (message.content.toLowerCase().includes('hello')) {
    await client.messages.send(channel.id, {
      content: `Hello, **${author.displayName}**! How can I help you?`,
    });
  }
});

// Listen for new members joining the workspace
client.on('member.joined', async (event) => {
  const { user, workspace } = event;

  // Find the #general channel
  const channels = await client.channels.list(workspace.id);
  const general = channels.find(c => c.slug === 'general');

  if (general) {
    await client.messages.send(general.id, {
      content: `Welcome to the team, **${user.displayName}**! 🎉`,
    });
  }
});

// React to reactions
client.on('reaction.added', async (event) => {
  if (event.emoji === '🚀') {
    // Someone added a rocket — trigger your CI pipeline, etc.
    console.log(`Rocket reaction by ${event.userId} on message ${event.messageId}`);
  }
});

await client.connect();

Available events

EventTypeScript typeDescription
message.createdMessageCreatedEventNew message in any subscribed channel
message.updatedMessageUpdatedEventMessage edited
message.deletedMessageDeletedEventMessage deleted
reaction.addedReactionEventReaction added to a message
reaction.removedReactionEventReaction removed
member.joinedMemberJoinedEventNew member joined workspace
member.leftMemberLeftEventMember left workspace
channel.createdChannelCreatedEventNew channel created
channel.archivedChannelArchivedEventChannel archived
slash_commandSlashCommandEventUser invoked a slash command

Slash commands

Slash commands let users interact with your bot by typing /commandname [args] in the message composer. Register them in Workspace Settings, then handle them in your bot.

Slash command payloads are sent to your Request URL (configured in Settings). You must respond with a JSON payload within 3 seconds, or Disqua shows a timeout error to the user.

src/slash-commands.ts — Express handler
import express from 'express';
import { createSlashCommandHandler, SlashCommandContext } from '@disqua/sdk/express';

const app = express();

const handler = createSlashCommandHandler({
  signingSecret: process.env.DISQUA_SIGNING_SECRET!,
  commands: {
    // /giphy <query> — post a gif
    giphy: async (ctx: SlashCommandContext) => {
      const query = ctx.args.join(' ');
      const gifUrl = await fetchGif(query);   // your own logic

      return ctx.respond({
        content: `Here's a GIF for "${query}":`,
        attachments: [{ type: 'image', url: gifUrl }],
        responseType: 'in_channel',           // visible to everyone
      });
    },

    // /remind @user in 1h <message>
    remind: async (ctx: SlashCommandContext) => {
      // Parse args, schedule a job, etc.
      return ctx.respond({
        content: `Reminder set!`,
        responseType: 'ephemeral',            // visible only to caller
      });
    },

    // /standup — starts an interactive standup
    standup: async (ctx: SlashCommandContext) => {
      return ctx.respond({
        content: "Let's do a standup! Fill in your update:",
        blocks: [
          {
            type: 'input',
            blockId: 'standup_input',
            label: 'What did you work on yesterday?',
            element: { type: 'plain_text_input', actionId: 'yesterday' },
          },
          {
            type: 'input',
            blockId: 'standup_today',
            label: 'What are you working on today?',
            element: { type: 'plain_text_input', actionId: 'today' },
          },
          {
            type: 'actions',
            elements: [
              { type: 'button', text: 'Submit', actionId: 'submit_standup', style: 'primary' },
            ],
          },
        ],
        responseType: 'ephemeral',
      });
    },
  },
});

app.post('/slack/commands', handler);

app.listen(3000);

SlashCommandContext properties

PropertyTypeDescription
commandstringCommand name without slash, e.g. "giphy"
textstringRaw argument string after the command
argsstring[]Arguments split by whitespace
userIdstringUUID of the user who invoked the command
channelIdstringUUID of the channel where it was invoked
workspaceIdstringUUID of the workspace
respond()functionSend a response (immediate or deferred)
ack()functionAcknowledge within 3s, respond later via ctx.respond()

Sending messages

// Simple text message (supports markdown)
await client.messages.send(channelId, {
  content: 'Hello **world**! Visit https://disqua.com',
});

// Reply in a thread
await client.messages.send(channelId, {
  content: 'This is a thread reply.',
  threadId: parentMessageId,
});

// Message with file attachment (use presigned upload flow first)
await client.messages.send(channelId, {
  content: 'Here is your report:',
  attachmentIds: [uploadedFileId],
});

// Update an existing message (bot's own messages only)
await client.messages.update(messageId, {
  content: 'Updated content',
});

// Delete a message (bot's own messages only)
await client.messages.delete(messageId);

// Add a reaction
await client.reactions.add(messageId, '👍');

// List recent messages in a channel
const { data: messages } = await client.messages.list(channelId, {
  limit: 50,
});

Block Kit — interactive messages

Block Kit lets you compose rich, interactive messages with buttons, inputs, select menus, and more. Blocks are rendered natively in the Disqua client.

Example — approval request with buttons
import { Blocks, Elements } from '@disqua/sdk';

await client.messages.send(channelId, {
  content: 'Deployment approval required:',
  blocks: [
    Blocks.Section({
      text: '*Deploy v2.4.1 to production?*\nService: `api` • Branch: `main` • Author: @jane',
    }),
    Blocks.Divider(),
    Blocks.Section({
      text: '*Changes:*\n• Fix rate limiting bug\n• Update dependency versions\n• Add new /health endpoint',
    }),
    Blocks.Actions({
      elements: [
        Elements.Button({
          text: 'Approve',
          actionId: 'deploy_approve',
          style: 'primary',
          value: JSON.stringify({ deployId: '123', version: '2.4.1' }),
          confirm: {
            title: 'Confirm deployment',
            text: 'This will deploy to production immediately.',
            confirmText: 'Deploy now',
            denyText: 'Cancel',
          },
        }),
        Elements.Button({
          text: 'Reject',
          actionId: 'deploy_reject',
          style: 'danger',
        }),
        Elements.Button({
          text: 'View diff',
          actionId: 'deploy_diff',
          url: 'https://github.com/your-org/repo/compare/v2.4.0...v2.4.1',
        }),
      ],
    }),
  ],
});

// Handle button clicks
client.on('block_action', async (event) => {
  if (event.actionId === 'deploy_approve') {
    const { deployId } = JSON.parse(event.value);
    await triggerDeployment(deployId);

    // Update the message to show approval
    await client.messages.update(event.messageId, {
      content: `Deployment approved by **${event.user.displayName}**`,
      blocks: [
        Blocks.Section({ text: 'Deployment to production is in progress...' }),
      ],
    });
  }

  if (event.actionId === 'deploy_reject') {
    await client.messages.update(event.messageId, {
      content: `Deployment rejected by **${event.user.displayName}**`,
      blocks: [],
    });
  }
});

Available block types

Blocks.Section

Markdown text, with optional accessory (image, button, select)

Blocks.Actions

Row of interactive elements (buttons, select menus)

Blocks.Input

Form input — text, multiline, date picker, user select

Blocks.Header

Large heading text

Blocks.Divider

Horizontal rule separator

Blocks.Image

Inline image with alt text and optional title

SDK API reference

All methods return Promises. Errors throw a typed DisquaError with code, message, and statusCode.

NamespaceMethods
client.messagessend(), list(), get(), update(), delete()
client.channelslist(), get(), create(), update(), archive(), addMember(), removeMember()
client.userslist(), get(), me()
client.reactionsadd(), remove()
client.filesupload(), get(), list(), delete()
client.on()Subscribe to a WS event
client.off()Unsubscribe from a WS event
client.connect()Establish WS connection
client.disconnect()Gracefully close WS connection

Deployment

A basic Disqua bot is a long-running Node.js process. Deploy it anywhere you run Node.js.

package.json scripts
{
  "scripts": {
    "build": "tsc",
    "start": "node dist/bot.js",
    "dev": "tsx watch src/bot.ts"
  }
}
.env
DISQUA_BOT_TOKEN=dqb_...
DISQUA_WORKSPACE_ID=01HQ6Z...
DISQUA_SIGNING_SECRET=...    # for slash commands

PM2 (VPS)

pm2 start dist/bot.js --name my-bot

Railway / Render

Push to GitHub, set env vars, deploy. Zero config.

Serverless (slash only)

Deploy just the HTTP handler to any serverless platform. No WS needed for slash commands.

Ready to build your bot?

Create a free Disqua account, set up a bot integration, and deploy in minutes.

Get started free