Skip to main content

Slack to Conversational Messaging

A composite graphic. The first half of the graphic shows a text message exchange. The second part of the graphic shows the same conversation in a Slack thread. A bot called SignalWire_Conversation_Bot forwards the text sent by the user, and a Slack user replies in the thread.
The end result.

Overview

In a day of office suites being replaced by slack channels, we figured it only appropriate to share just one of the many ways that the Slack APIs and SignalWire APIs can be used in unison. This application will associate your SignalWire phone number and your Slack Workspace so that you may send and receive messages all through the Slack interface.

What do you need to run this code?

View the full code on our Github here!

This guide uses the Python SignalWire SDK, for a guide on installation click here.

You will also need to have a SignalWire phone number, and have your SignalWire Space credentials ready to go (API Token, Space URL, and Project ID). You can find all of these credentials in an easily-copyable format under the API tab of your SignalWire Space. For more information on where to find this stuff, check out our Navigating your SignalWire Space section!

Ngrok will be used to expose our application to both SignalWire and Slack via webhooks. Or, you can host this code on a server of your choice instead

Lastly, you will need a slack account and workspace. You can sign up for a free account here

How to Run the Application

Configure Slack and SignalWire Webhooks

The first goal to accomplish is to configure our Slack and SignalWire webhooks so that all events that occur over either platform will be sent to our application. In order to complete the configuration of the slackbot, we must enter a webhook that verifies a challenge request. So, we will begin by setting this up and then continue on with configuring the slackbot.

In order to create our webhooks, we must run our application as well as set up our server tunnel. We will be using ngrok for our tunnel, which will give us the base of our webhook, and then the application will define the path that we must follow.

Begin by installing all necessary packages. You can accomplish this by running the following command in the terminal of your application's root directory.
pip install flask signalwire twilio slackclient python-dotenv

Once all packages are installed, run the application by entering the following command in your terminal.
python main.py

Now that our application is running, we can see it live on http://localhost:5050 so we must tunnel it using ngrok to be exposed to the internet. Once you have ngrok installed, you can run the following command in your terminal.
ngrok http 5050

Your terminal will then transform into an ngrok web interface for our application. From the screenshot below, we can see that our two webhooks for this example will have the domain https://eebe18050de2.ngrok.io and then can be differentiated by appending /slack or /signalwire, as seen in the app.py file.

With the ngrok standard account your forwarded domain will be an arbitrary scramble of alphanumeric characters which change every time you run ngrok so be sure to keep your tunnels running, or update your webhooks as needed.

A screenshot of an ngrok tunnel text interface. The Forwarding domains are shown to be dymanically generated URLs.
Ngrok.

Set Up Slack Bot

Now that we have our slack webhook, we are ready for all of the steps to create our slackbot. After you have created your account, navigate to your Slack apps Dashboard and select Create New App then From Scratch . Continue on with naming your bot and assign it to the appropriate workspace.

A screenshot of the Create New App popup in Slack. There is a text field for the App Name, which is set to SignalWire_Bot. There is also a dropdown menu titled Pick a Workspace to develop your app in, which is set to Stark Industries.
App creation UI in Slack.

Once you have created your application, you should see a page that looks like this.

A screenshot of the Basic Information page. There is information about building apps for Slack, ways to add features and functionality, and installing the app to the desired workplace.
Application screen in Slack.

We must then set up the appropriate Scopes, which is the slack equivalent of allowing specific permissions. To do this, click on Permissions and then scroll down to Bot Token Scopes . Enter the following OAuth Scopes:

  • channels:history
  • channels:join
  • channels:read
  • product:chat:write
  • product:chat:write.customize
  • product:chat:write.public
A screenshot of the Scopes page for the Slack app, showing the described OAuth Scope items added under Bot Token Scopes.
Scopes configuration in Slack.

Now that we have given our slackbot permission to do just about whatever it wants in Slack, it is time to revisit the webhook we created previously through ngrok. We can connect this slackbot to our application by navigating to ** Event Subscriptions ** on the left menu and enable event subscriptions. This is the slack equivalent to a status callback within SignalWire.

From the configuring webhooks section, we noted that our slack webhook would be https://eebe18050de2.ngrok.io/slack for this example. Your Request URL will look similar but your own ngrok domain will need to be used.

A screenshot of the Event Subscriptions page within the Features setting category. The ngrok domain is pasted within the Request URL field.
Event subscriptions configuration in Slack.

As noted in the above screenshot, you will need to scroll to ** Subscribe to events on behalf of users ** and subscribe to:

  • message.channels

Lastly, click ** Save Changes ** and it is time to go live with our bot.

Navigate back to ** Basic Information ** and click on ** Install Your App ** to generate your Slack API Credentials, specifically the Bot User OAuth Token.

Take a note of the Bot User OAuth Token because this will later be entered into our .env file.

A screenshot of the OAuth Tokens for Your Workplace pane. Text fields allow the user to easily copy the User OAuth Token and the Bot User OAuth Token.
Your OAuth tokens.

Connecting to SignalWire

Now that Slack is in communication with our application, we must now associate a SignalWire phone number that will be using Slack as the front end for their conversational messaging. How will we connect this? You guessed it, the webhook we created earlier!

If you need to Create a SignalWIre Account or Purchase a number please so now.

Within your SignalWire Dashboard, we can associate this webhook to our phone number by navigating to ** Phone Numbers ** and then ** Edit Settings **.

We will only be dealing with messaging so by scrolling down to ** Messaging Settings ** we can handle incoming messages using LaML Webhooks and then entering our ngrok domain with the /signalwire appended. And make sure to ** Save **!

A screenshot of the Phone Numbers tab in a SignalWire Space, specifically the Messaging Settings section.
SignalWire Messaging settings.

You can now test this application out by messaging your SignalWire number from your own device and look out for messages in the #general channel. ** You must reply in a thread to the incoming message to initiate a conversation**.

One Final Step

As a final step, ensure that you add your bot to your Slack channel by going into your channel's settings. Otherwise, your application will not be notified of replies in the Slack channel.

A screenshot of the Integrations settings pane of the #general channel in a Slack workspace. SignalWire_Conversation_Bot is shown added in the Apps section.
Make sure to add your bot to your Slack channel.

Step By Step Code Walkthrough

Within the Github repository you will find 2 files that we will be looking at:

  • example.env
  • app.py

Setup Your Environment File

  1. Copy from example.env and fill in your values
  2. Save new file called .env

Your file should look something like this

SLACK_BOT_TOKEN=xoxb-XXXXXXXXXXXX-XXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXXX
SIGNALWIRE_PROJ_ID=649dc08e-3558-XXXX-XXXX-XXXXXXXXXXXXXX
SIGNALWIRE_API_TOKEN=PT0THISISAVERYLONGCODETHATBEGINSWITHPT
SIGNALWIRE_NUMBER=+1808444XXXX
SIGNALWIRE_SPACE_URL=EXAMPLE.signalwire.com

Walkthrough of app.py

The beginning to our python code is simply starting our flask app and initializing our SignalWIre/ Slack Clients. We will use these clients to tap into the individual SDKs later in the code. You will notice that the Slack and SignalWire APIs are not very different from eachother.

load_dotenv()
app = Flask(__name__)

# Declare our Slack and SignalWire
slack_token =os.getenv("SLACK_BOT_TOKEN")
slack_client = slack.WebClient(slack_token)
client = signalwire_client(os.getenv("SIGNALWIRE_PROJ_ID"),os.getenv("SIGNALWIRE_API_TOKEN"),signalwire_space_url = os.getenv('SIGNALWIRE_SPACE_URL'))

Next, we define the /signalWire route to our application. This is the route that is taken when a message is sent to your SignalWire number, and is then needed to be posted to slack.

This flow begins by reading the From and Body of a message, which was sent to our application via JSON file from SignalWire. These values are then stored and are used to post the message to slack from the Slack SDK. The explicit API call to send a chat message is seen on line 6 with slack_message = slack_client.chat_postMessage().

@app.route('/signalwire', methods=['POST'])
def signalwire_to_slack_flow():
from_number = request.form['From']
sms_message = request.form['Body']
message = f"Text message from {from_number}: {sms_message}"
slack_message = slack_client.chat_postMessage(
channel='#general', text=message, icon_emoji=':robot_face:')
response = MessagingResponse()
return Response(response.to_xml(), mimetype="text/html"), 200

Next, we need to define the /slack route. This is going to be the route taken for when an event occurs on Slack, so we need to take this response apart, make sense of it, and then use SignalWire's Create a Message API to send a text message back to the user.

Notice In line 6, we see the first filtration of JSON responses with the implementation of parse_message but we will further discuss that function later. Once the response has gone through and passed all the tests, we can then extract the body and to number to be entered into our Create a Message API.

@app.route('/slack', methods=['POST'])
def slack_to_signalwire_flow():
attributes = request.get_json()
if 'challenge' in attributes:
return Response(attributes['challenge'], mimetype="text/plain")
incoming_slack_message_id, slack_message, channel = parse_message(attributes)
if incoming_slack_message_id and slack_message:
to_number = get_customer_number(incoming_slack_message_id, channel)
if to_number:
to_number = '+' + to_number
messages = client.messages.create(
to=to_number, from_=os.getenv("SIGNALWIRE_NUMBER"), body=slack_message)
return Response(), 200
return Response(), 200

The remainder of the code are some filler functions that make these routes possible. I want to shine some light on the parse_message function because this is what aided greatly in the /slack route to determine when to trigger the SignalWire APIs.

Not all events that occur in a channel need to warrant a text message being sent. What this means is that the Slack API sends a lot of information (JSON data) that is not necessarily needed. So, a filter was created that would only return the attributes of events we care about, and only pulling the information that we need from them.

If a message is sent in a thread, then we are able to dig through the individual attributes of the message to ensure that we are replying back to the correct number and conversations can stay consistent.

def parse_message(attributes):
if 'event' in attributes and 'thread_ts' in attributes['event']:
return attributes['event']['thread_ts'], attributes['event']['text'], attributes['event']['channel']
return None, None, None

def get_customer_number(incoming_slack_message_id, channel):
data = slack_client.conversations_history(channel=channel, latest=incoming_slack_message_id, limit=1, inclusive=1)
if 'subtype' in data['messages'][0] and data['messages'][0]['subtype'] == 'bot_message':
text = data['messages'][0]['text']
phone_number = pull_number_from_slack_message(text)
return phone_number
return None

def pull_number_from_slack_message(text):
data = re.findall(r'\w+', text)
if len(data) >= 4:
return data[3]
return None

if __name__ == '__main__':
app.run( port=5050)

Wrap Up

As our workplaces change, so do our methods of telecommunication. Our goal here at SignalWire is to simplify your telecommunication methods so that they can all work symbiotically, this begins with powerful API integrations.

In the same way that your web server can be notified of events occurring in your SignalWire Dashboard, your web server can be notified of events occurring within Slack to trigger actions within each other's platforms in real-time. This means the possibilities are endless!

Whether you want to implement a Slack alert for when you run into an error, receive a voicemail, or even when you hit your sales call goal for the day, we are excited to see how you elevate your SignalWire experience with this new integration.

Resources

Github Repo
Slack apps dashboard
Python SignalWire SDK
Ngrok

Sign Up Here

If you would like to test this example out, create a SignalWire account and Space.

Please feel free to reach out to us on our Community Slack or create a Support ticket if you need guidance!