Using a Slack app as a communication portal between different workspaces

Slack shared channels build a bridge between two workspaces, but what about the bridges between app developers and their users? Learn how to support your app users via chat.

Griffin Solot-Kehl · Sep 17, 2019

If a user wants support on the Slack app they installed, why email when they can ask the bot user? Keeping engagement up with your app is important, but most support cases force users to close Slack. I decided to build an app that could let a bot user act as a portal of communication between workspaces. The process ended up being both fun and enlightening, so I decided to document my process for you here.

Finding an entry point

We want the users of our apps to be able to talk directly to us. The most natural way is through the app's direct message channel. Using the Slack Events API to listen for message.im events, we can receive a notification on each message. Here's what a typical payload looks like for that webhook:

"parsed_body": {
"token": "<token>",
"team_id": "XXXXXXXXX",
"api_app_id": "XXXXXXXXX",
"event": {
"client_msg_id": "b6d3ccac-37bf-4d4d-a3ba-44efcc9004b7",
"type": "message",
"text": "Hi there!",
"user": "XXXXXXXXX",
"ts": "1568666605.000400",
"team": "XXXXXXXXX",
"channel": "XXXXXXXXX",
"event_ts": "1568666605.000400",
"channel_type": "im"
},
"type": "event_callback",
"event_id": "XXXXXXXXX",
"event_time": 1568666605,
"authed_users": ["XXXXXXXXX"]
}

The important parameters to look at here are text, which contains the message we want to send to our home workspace, and user, which is also the channel id for this conversation based on how Slack DMs work. A parameter we do not see however is anything that tells us what the user's name is, which can help make support messages feel more personal. This can be solved by another API call to Slack's users.info method, using the user ID as the user argument, and pulling the real_name from the returned profile array.

At this point, I thought about how it is also important to be able to differentiate between the different workspaces that have the app installed and would be trying to communicate with your "home" workspace, which I have dubbed "away" workspaces. Having context is important, as there can be people with the same name, or different protocols and support levels depending on the place somebody comes from, and having to remember workspaces by their ID string is confusing. Thankfully, Slack's team.info method will solve this similarly to user.info, returning team.name for the current workplace the bot is in.

One way street

Connecting all the data from the webhook into the home channel turned out to be a very simple solution. I could have two data connections to Slack: one for my home workspace, and one for the away workspaces. The question instead, was in what way to post them. I considered two potential solutions:

  1. For each conversation with a user in an away workspace I'd create a new channel in my home workspace named something like #app-<away_id>-<away_user> (thanks 80-character channel names!). Away messages would be posted in the home channel and vice-versa.
  2. Have a single channel in my home workspace (#app-support) with new away messages dropped right in. Replies would be sent to the away workspace via threads (hooray for thread!)

The channel by channel solution does have its advantages in being easier to organize and close based on user. If you want a specific user, you know exactly where to find them, instead of needing to look through a channel history. It does increase channel clutter and decentralizes your support channel. The threading solution creates a centralized channel for support, but assumes that most discussions will be happening in real time, as there is no sort newest to front. The threading solution also made implementation much easier, as the information I got from the away channel could just be posted as a message to the specified channel in the home workspace. With some simple Slack formatting, I could get it to look like this:

Completing the circle

Now that I had away messages appearing in my home channel, I needed to make responses to those threads forward to the correct user in the away workspace. To make this work, I was able to borrow a trick I used building a Slack to Google Sheets forwarding app. Each Slack message comes with a ts field, the timestamp that uniquely identifies the message When the message is a reply to a thread, it will both contain its own ts value, but also a thread_ts value that represents the timestamp/unique id of the parent message. When a user in our home channel replies to a thread, here’s an example payload of what our webhook receives:

"parsed_body": {
"token": "<token>",
"team_id": "XXXXXXXXX",
"api_app_id": "XXXXXXXXX",
"event": {
"bot_id": "XXXXXXXXX",
"type": "message",
"text": "hello!",
"user": "XXXXXXXXX",
"ts": "1568682463.000100",
"team": "XXXXXXXXX",
"channel": "XXXXXXXXX",
"event_ts": "1568682463.000100",
"channel_type": "im"
},
"type": "event_callback",
"event_id": "EvN2TBAEKD",
"event_time": 1568682463,
"authed_users": [
"XXXXXXXXX"
]
}

To store metadata into a Slack message, I used Block Kit to format my message. I needed to save the workspace ID as well as the user ID of the sender into the body of a message so that I could find out the correct place to forward a reply. Block Kit has fields that are stored in the JSON of a Slack message, but do not show up, so I used them accordingly, as you can see here:

"channel" : @channel,
"text" : @workspace,
"blocks" : [
{
"type": "context",
"block_id": @user,
"elements": [
{
"type": "mrkdwn",
"text": @title
}
]
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": @body
}
}
]

I could then find the parent message by running a channels.history query looking for a message whose ts matched the thread_ts of the reply. Once I have the correct message, I can pull the values back out of the message as I would any other JSON object to be forwarded back to the away workspace.

Closing the wormhole

This week, Slack released shared channels out of beta which allows communication between two workspaces using a magically connected channel. This solution seems focused on cross team communication rather my single user support channel. The benefit of building your own solution is complete customization to work exactly as you want. Sometimes the solution you want has already been built for you, but for those times that it isn't, it's time to break out the API documentation.

Check out my app if you want to see what cross communication looks like. You can fork it or copy the logic into your own Slack app. Also look at our Slack landing page for more information and help to get started.