Go to Slack

Retrieving messages

Slack apps tend to encounter messages most often when receiving them in Events API payloads or in request payloads when users invoke slash commands or custom actions.

However, there are some occasions where it might be necessary for an app to actively seek out a message and find it in the wild.

This guide will show you how to access the history of a Slack conversation and then pull out the details of a specific message. It will also show you how to identify threaded messages, and retrieve the replies in a thread.


Getting started with retrieving messages

One thing you'll need before starting is a Slack app. If you don't have one yet, here's a very quick guide to help you create one. Make sure you create the app in a test workspace, because you're going to be requesting some major data access permissions.

After you've done that, come back here and keep reading.

Requesting the necessary permissions

In a raw state, your app will only be able to view messages that are sent to it. In order to read anything else, it will need to request scopes to get permission.

There are lots of scopes available, and you can read our OAuth guide for more information on why they're needed, and what they do. For this guide, we need to add two scopes.

The first is channels:read. That scope lets your app retrieve a list of all the public channels in a workspace so that we can pick one to retrieve a message from.

The second is channels:history. This will allow your app to view all the messages within any public channel in a workspace.

Requesting these permissions is easy:

  1. Load up the settings for your app from the app management page.
  2. In the navigation menu, choose the OAuth & Permissions feature.
  3. Scroll down to the Scopes section, and pick channels:read and channels:history from the drop down menu.
  4. Click save changes.
  5. Scroll back to the top of this page and look for the button that says Install App to Workspace (or Reinstall App if you've done this before). Click it.

You'll now see a permission request screen to install your app to its original workspace.

If you had already installed your app to its original workspace before, you might still see the permissions screen if the scopes you just added weren't previously granted to your app.

Authorize your app and you should see a success message. On the OAuth & Permissions settings page you're brought back to, you should now see an OAuth access token available.

Grab this token and store it for later, as we'll use that token to make some Web API calls.

Finding a conversation

Using the token you generated above, we'll make an API call to the conversations.list method.

With the channels:read permission you requested, this method will return a list of public channels in the workspace. Within that list, we'll be able to find a specific id of the conversation that we want to access.

Here's a cURL example that shows you the general principle behind making this API call. You can adjust to your own chosen code language, substituting the token you generated:

GET https://slack.com/api/conversations.list?token=YOUR_TOKEN_HERE
Content-type: application/json

You'll get back a JSON object, with a channels array containing all the public channels that your app can see. You can find your channel by looking for the name in each object.

When you've found the matching channel, make note of the id value, as you'll need it for the next step.

Retrieving conversation history

With the information you just collected, and your previously generated token, we can make another Web API call to conversations.history:

GET https://slack.com/api/conversations.history?token=YOUR_TOKEN_HERE&channel=CONVERSATION_ID_HERE
Content-type: application/json

Dropping in your token and id values, this method will return a list of a list of messages and events within the chosen conversation. The method's reference guide contains example responses and some explanations of the data included within.

The important part for this guide is the messages array, which contains every message in the conversation. For the next part of the guide, you need to grab the ts value of one of these message objects, and we'll explain why in a second.

Retrieving individual messages

The structure of message objects retrieved via Slack APIs is very similar to the general structure of a message payload you want to publish.

There are a few additional fields that describe the author (such as user or bot_id), but there's also an additional ts field. The ts value is essentially the ID of the message, guaranteed unique within the context of a channel or conversation.

They look like UNIX/epoch timestamps, hence ts, with specified milliseconds. They'll even sort like the same. But they're message IDs, even if they're partially composed in seconds-since-the-epoch.

The ts of a message can be used in many operations such as replying to it in a thread, or modifying the message. But it can also be used to retrieve the message by itself.

Using the ts from the previous step with the information previously collected, and your token, we can make a slightly different call to conversations.history:

GET https://slack.com/api/conversations.history
  ?token=YOUR_TOKEN_HERE
  &channel=CONVERSATION_ID_HERE
  &latest=YOUR_TS_VALUE
  &limit=1
  &inclusive=true
Content-type: application/json

You can read the conversations.history reference guide for a fuller explanation of what these additional parameters do, but essentially we're telling the API to return one result from the conversation's history, using the message ts as a starting point.

Other encounters with individual messages

As we mentioned before, your app is likely to encounter individual messages in ways other than a direct retrieval.

If you subscribe to certain Events API types, you will receive a notification payload from each event that may contain a message object. For example, the app_mention event will be sent whenever someone mentions your app in a message, and that source message will be included in the notification payload.

After you publish a message, you'll receive an API response that will include a message object containing the newly created message.

Slash commands and custom actions both send similar request payloads when a user invokes them in Slack. Those payloads contain a message object that describes the source message of the invocation.

Interactive components in messages will also send request payloads when the components are used. Again, these payloads will contain a message object that describes the source of the interaction.

In all of the above cases, the structure of the message object is similar to that outlined above. Read the relevant individual guides to see examples of the kinds of payloads or responses your app will receive.


Threaded messages

We've explained the basics behind threaded messages elsewhere (familiarize yourself with our terminology if you haven't already, because it can get tangled), but how do you know when the message you've received or retrieved is part of a thread? And if you have a parent message, how do you retrieve all the replies to it? Read on to find out.

Spotting threads

There's a few steps involved with spotting a thread and then understanding the context of a message within it. Let's unspool them:

  1. Detect a threaded message by looking for a thread_ts value in the message object. The existence of such a value indicates that the message is part of a thread.
  2. Identify parent messages by comparing the thread_ts and ts values. If they are equal, the message is a parent message.
  3. Threaded replies are also identified by comparing the thread_ts and ts values. If they are different, the message is a reply.

One quirk of threaded messages is that a parent message object will retain a thread_ts value, even if all its replies have been deleted.

Retrieving threads

Use conversations.replies to retrieve replies to a specific message, regardless of whether it's from a public or private channel, direct message, or otherwise.

Here's an example response from conversations.replies:

{
    "ok": true,
    "messages": [
        {
            "type": "message",
            "user": "U061F7AUR",
            "text": "Was there was there was there what was there was there what was there was there there was there.",
            "thread_ts": "1482960137.003543",
            "reply_count": 3,
            "replies": [
                // Deprecated, to be removed on October 18, 2019.
                {
                    "user": "U061F7AUR",
                    "ts": "1483037603.017503"
                },
                {
                    "user": "U061F7AUR",
                    "ts": "1483051909.018632"
                },
                {
                    "user": "U061F7AUR",
                    "ts": "1483125339.020269"
                }
            ],
            "reply_users": ["U061F7AUR", "U061F7AUR", "U061F7AUR"],  // max 5
            "reply_users_count": 3,
            "latest_reply": "1483125339.020269",
            "subscribed": true,
            "last_read": "1484678597.521003",
            "unread_count": 0,
            "ts": "1482960137.003543"
        },
        {
            "type": "message",
            "user": "U061F7AUR",
            "text": "Shutters shut and shutters and so shutters shut and shutters and so and so shutters and so shutters shut and so shutters shut and shutters and so.",
            "thread_ts": "1482960137.003543",
            "parent_user_id": "U061F7AUR",
            "ts": "1483037603.017503"
        },
        {
            "type": "message",
            "user": "U061F7AUR",
            "text": "Let me recite what history teaches. History teaches.",
            "thread_ts": "1482960137.003543",
            "parent_user_id": "U061F7AUR",
            "ts": "1483051909.018632"
        },
        {
            "type": "message",
            "user": "U061F7AUR",
            "text": "I love you Alice B. Toklas and so does Gertrude Stein.",
            "thread_ts": "1482960137.003543",
            "parent_user_id": "U061F7AUR",
            "ts": "1483125339.020269"
        }
    ],
    "has_more": true,
    "response_metadata": {
        "next_cursor": "bmV4dF90czoxNDg0Njc4MjkwNTE3MDkx"
    }
}

This conversations.replies method returns a messages array that first contains the parent message object, followed by message objects for all the threaded replies.

Want to learn how to reply to threads? Read our guide to sending messages.