Slack to Conversational Messaging
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.
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.
Once you have created your application, you should see a page that looks like this.
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
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.
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.
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 **!
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**.
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.
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
- Copy from example.env and fill in your values
- 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!