Slack Developer Kit for Node.js
Go to GitHub

RTM API client

This package includes an RTM client that makes it simple to use the Slack RTM API. Here are some of the goodies you get right out of the box:

  • Event emission - use it like any other Node EventEmitter
  • Message queuing
  • Connection state management
  • Keep-alive processing
  • Automatic reconnection
  • Message serialization
  • Convenience methods for sending typing, message, and presence_sub events.
  • Error handling
  • Logging
  • Configurability

An RTM client allows your app to listen for incoming events as well as send outgoing events. It’s a quick solution for many apps – especially for those that may only be about to create outgoing connections due to firewall restrictions, or only ever need to connect to one workspace.

But the RTM client does not have access to the full power of the Slack platform, including message attachments, buttons, menus, and dialogs. It also does not scale well for apps that are connected to many workspaces because it is stateful. This also causes problems when the workspace uses Shared Channels. The most robust app building solution is through a combination of the Web API and the Events API.

Here are some of the common recipes for using the RTMClient class.

Connecting and sending a message

Your app will interact with the RTM API through the RTMClient object, which is a top level export from this package. You need to instantiate it with a token, usually a bot token.

const { RTMClient } = require('@slack/client');

// An access token (from your Slack app or custom integration - usually xoxb)
const token = process.env.SLACK_TOKEN;

// The client is initialized and then started to get an active connection to the platform
const rtm = new RTMClient(token);

// This argument can be a channel ID, a DM ID, a MPDM ID, or a group ID
// See the "Combining with the WebClient" topic below for an example of how to get this ID
const conversationId = 'C1232456';

// The RTM client can send simple string messages
rtm.sendMessage('Hello there', conversationId)
  .then((res) => {
    // `res` contains information about the posted message
    console.log('Message sent: ', res.ts);

Receiving messages

The client will emit message events whenever a new message arrives in a channel where your bot is a member. The client can handle message events using the on('message', messageHandler) method.

const { RTMClient } = require('@slack/client');
// The initialization code shown above is skipped for brevity

rtm.on('message', (message) => {
  // For structure of `message`, see

  // Skip messages that are from a bot or my own user ID
  if ( (message.subtype && message.subtype === 'bot_message') ||
       (!message.subtype && message.user === rtm.activeUserId) ) {

  // Log the message
  console.log(`(channel:${}) ${message.user} says: ${message.text}`);

Combining with the WebClient

The example below shows an example where the RTMClient and WebClient are used together. The web API method channels.list is used to get a current list of all channels and can be iterated over to find one where the bot is a member. Once we have a channel ID, sending a message is as easy as calling rtm.sendMessage().

const { RTMClient, WebClient } = require('@slack/client');

const token = process.env.SLACK_TOKEN;

// The client is initialized and then started to get an active connection to the platform
const rtm = new RTMClient(token);

// Need a web client to find a channel where the app can post a message
const web = new WebClient(token);

(async () => {
  // Load the current channels list asynchronously
  const res = await web.channels.list()

  // Take any channel for which the bot is a member
  const channel = res.channels.find(c => c.is_member);

  if (channel) {
    // We now have a channel ID to post a message in!
    // use the `sendMessage()` method to send a simple string to a channel using the channel ID
    const msg = await rtm.sendMessage('Hello, world!',;

    // `msg` contains information about the message sent
    console.log(`Message sent to channel ${} with ts:${msg.ts}`);
  } else {
    console.log('This bot does not belong to any channel, invite it to at least one and try again');

Lifecycle events

The client emits many other types of events, besides message events. The following table describes the other types of events you can attach a handler function to, using the .on(type, eventHandler) method. The arguments column describes the arguments that eventHandler would be called using - the data related to that event. In the table, most of the events are lifecycle events, and relate to the state of the connection. The last few rows describe platform events (e.g. slack_event, {type}, etc.), whose structures are defined in the documentation and labeled with the RTM tag.

Event Name Arguments Description
disconnected   The client is not connected to the platform. This is a steady state - no attempt to connect is occurring.
connecting   The client is in the process of connecting to the platform.
authenticated (connectData) - the response from rtm.connect or rtm.start The client has authenticated with the platform. This is a sub-state of connecting.
connected   The client is connected to the platform and incoming events will start being emitted.
ready   The client is ready to send outgoing messages. This is a sub-state of connected
disconnecting   The client is no longer connected to the platform and cleaning up its resources. It will soon transition to disconnected.
reconnecting   The client is no longer connected to the platform and cleaning up its resources. It will soon transition to connecting.
error (error) An error has occurred. Check error.code against the dictionary in the top-level export called ErrorCode
unable_to_rtm_start (error) A problem occurred while connecting, a reconnect may or may not occur. Use of this event is discouraged since disconnecting and reconnecting are more meaningful.
slack_event (eventType, event) An incoming Slack event has been received.
{type} ('{type}', event) An incoming Slack event of type {type} has been received.
{type}::{subtype} ('{type}::{subtype}', event) An incoming Slack event of type {type} and subtype {subtype} has been received.
raw_message (message) A websocket message arrived. The message is an unparsed string. Use of this event is discouraged since slack_event is more useful.

Listening for message subtypes

There’s a shortcut syntax for listening to specific message subtypes, just format the event type as message::${message_subtype}. See the example below.

const { RTMClient } = require('@slack/client');
// The initialization code shown above is skipped for brevity

rtm.on(`message::channel_name`, (event) => {
  // For structure of `event`, see
  console.log(`A channel was renamed from ${message.old_name} to ${}`);

When the program is running and you rename a channel your bot is a member of, you should see the logged message.

Handling other events

Anything that happens in a Slack workspace which is visible to the bot (i.e. happens in a channel to which the bot is a member and visible to its scopes) is communicated as an event as well. For a complete list of other events, see

const { RTMClient } = require('@slack/client');
// The initialization code shown above is skipped for brevity

rtm.on('reaction_added', (event) => {
  // For structure of `event`, see
  console.log(`User ${event.user} reacted with ${event.reaction}`);

Subscribing to presence updates

Polite people try not to inundate their colleagues with messages when they know they are offline. We can teach a bot the same etiquette by subscribing to presence and status information for the users with which it interacts.

You may not need to subscribe to presence updates if your bot is okay with fetching the user’s status on-demand using the WebClient.users.getPresence() method.

If you do prefer to subscribe to presence updates, each time the client connects, your bot needs to send a list of user IDs that its interested in using the subscribePresence(userIds) method. The userIds argument is an array of user IDs and the list must be complete – any user IDs not included are unsubscribed from your bot.

You can get more efficient presence_change events by using the batch_presence_aware option while connecting. If you set the option to true your bot will receive presence_change events with a users property that contains an array of user IDs (instead of a user property with a single user ID). See more about the event:

The following example shows how a bot would keep a timecard for users to record their active time or away time.

const { RTMClient, WebClient } = require('@slack/client');

// An access token (from your Slack app or custom integration - usually xoxb)
const token = process.env.SLACK_TOKEN;

// Initialize the clients
const rtm = new RTMClient(token);
const web = new WebClient(token);
  batch_presence_aware: true,

// Timecard data - In order for this data to survive a restart, it should be backed by a database.
// Keys: user IDs (string)
// Values: presence updates (array of { timestamp: number, presence: 'away'|'active' })
const timecards = new Map();
const getTrackedUsers = () => Array.from(timecards.keys())
const updateTimecard = (userId, presence) => {
  if (!timecards.has(userId)) {
    timecards.set(userId, []);
  const userRecord = timecards.get(userId);
  userRecord.push({ timestamp:, presence });
const removeTimecard = (userId) => timecards.delete(userId);

// Timecard data is tracked for users in a pre-defined channel
const timeTrackingChannelId = 'C123456';

// Inform the platform which users presence we want events for

// See:
rtm.on('presence_change', (event) => {
  event.users.forEach(userId => updateTimecard(userId, event.presence));

// See:
  if ( === timeTrackingChannelId) {
    // When a user joins, get that user's current presence in order to update the timecard
    web.users.getPresence({ user: event.user })
      .then(resp => {
        updateTimecard(event.user, resp.presence);
        // Update subscriptions

// See:
  if ( === timeTrackingChannelId) {
    // When a user leaves, the timecard records are deleted
    // Update subscriptions

Workspace cache on connect

The useRtmConnect option on RTMClient initialization can give you control of how much workspace state you receive when you connect and receive the authenticated event.

If you set the value to false (not recommended for large teams), you’ll receive a cache the relevant client state for your app. See the response in the rtm.start reference documentation for a description of the workspace cache.

This should be treated as a cache because this information can go stale quickly if you aren’t listening for every event to update the state. Instead, we recommend storing only the state your app needs to operate in a variable. Then updating that variable in each event that might mutate the state.

A simpler alternative is to fetch the state you need from the Web API whenever you need it.

Customizing the logger

The RTMClient also logs interesting events as it operates. By default, the log level is set to LogLevel.INFO and it should not log anything as long as nothing goes wrong.

You can adjust the log level by setting the logLevel option to any of the values found in the LogLevel top-level export.

const fs = require('fs');
const { RTMClient, LogLevel } = require('@slack/client');

// increased logging, great for debugging
const rtm = new RTMClient(token, { logLevel: LogLevel.DEBUG });

There are four logging levels: LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR. Here they appear in order of increasing severity, which means that using LogLevel.ERROR will output the least number of messages, and only the most important.

You can also capture the logs without writing them to stdout by setting the logger option. The option should be set to an object that has the following methods:


Method Parameters Return type
setLevel() level: LogLevel void
setName() name: string void
debug() ...msgs: any[] void
info() ...msgs: any[] void
warn() ...msgs: any[] void
error() ...msgs: any[] void

NOTE: While the use of logging functions is deprecated, the logger option will still currently accept a function whose method signature matches fn(level: string, message: string)

const fs = require('fs');
const { RTMClient, LogLevel } = require('@slack/client');

// open a file to write logs
// TODO: make sure to call `logStream.end()` when the app is shutting down
const logStream = fs.createWriteStream('/tmp/app.log');

const token = process.env.SLACK_TOKEN;
logStream.on('open', () => {
  const rtm = new RTMClient(token, {
    // write all messages to disk
    logger: {
      debug(...msgs) { logStream.write(JSON.stringify(msgs)); }
      info(...msgs) { logStream.write(JSON.stringify(msgs)); }
      warn(...msgs) { logStream.write(JSON.stringify(msgs)); }
      error(...msgs) { logStream.write(JSON.stringify(msgs)); }
      // these methods are noops because this custom logger will write everything to disk (all levels)

Custom agent for proxy support

In order to pass outgoing requests through an HTTP proxy, you’ll first need to install an additional package in your application:

$ npm install --save https-proxy-agent

Next, use the agent option to configure with your proxy settings.

const HttpsProxyAgent = require('https-proxy-agent');
const { RTMClient } = require('@slack/client');

// in this example, we read the token from an environment variable. its best practice to keep sensitive data outside
// your source code.
const token = process.env.SLACK_TOKEN;

// its common to read the proxy URL from an environment variable, since it also may be sensitive.
// NOTE: for a more complex proxy configuration, see the https-proxy-agent documentation:
const proxyUrl = process.env.http_proxy || '';

// To use Slack's RTM API:
const web = new RTMClient(token, { agent: new HttpsProxyAgent(proxyUrl) });