Blog Search Results for

How to Connect Discourse to Slack

Brandon Cannaday
Brandon Cannaday 7 minute read

At Losant we use Discourse for our forums, and it works really well. Discourse offers email notifications for when someone posts a message, but what we really wanted was a Slack message. To my surprise, Slack and Discourse don't already have an integration, but this is something easily accomplished using a Losant workflow. This tutorial will demonstrate how to post a message in Slack whenever a user posts a message in Discourse.

slack-screenshot.png

The Discourse API

Unfortunately, Discourse does not yet support webhooks, which would make this a lot easier, but it does fortunately have a nice API. The API endpoint we're going to use is entirely public, because it simply returns data that anyone can see on your forums anyway, so we don't have to worry about any authentication.

The endpoint that returns all recent posts, ordered by date, is:

https://forums.example.com/posts.json

Where forums.example.com is simply the URL of your forums (the same one you use to access it with a browser). Losant's recent forum posts can be see here: https://forums.losant.io/posts.json

This will return a collection of posts ordered by newest to oldest. Below is a truncated version of the data returned. I have removed all the fields that we won't be using during this tutorial.

{
  "latest_posts": [
    {
      "id": 162,
      "name": "Ron Redden",
      "username": "Ron_Redden",
      "created_at": "2016-03-20T22:25:38.546Z",
      "post_number": 5,
      "topic_id": 81,
      "topic_slug": "havent-received-my-builder-kit",
      "raw": "Thanks everyone for responding, I'll look for my kit to arrive in the next few weeks then!",
      ...
    },
    {
      "id": 161,
      "name": "Charlie Key",
      "username": "zwigby",
      "created_at": "2016-03-20T22:01:15.364Z",
      "post_number": 4,
      "topic_id": 81,
      "topic_slug": "havent-received-my-builder-kit",
      "raw": "We're shipping out kits as fast as we can. As Brandon mentioned the main compute module we use is on backorder. We'll send an update out to everyone this week.",
      ...
    },
    ...
  ]
}

As we can see, Discourse has an incrementing number ID for each post, which means newer posts have a greater ID than older posts. This will be important for us later to determine which posts we haven't already sent to Losant.

The Losant Workflow

Now that we know how to get the latest Discourse posts, we need to periodically request them using a Losant workflow and then send new ones to Slack.

If you haven't already, sign up for Losant and create an application. Next, create a workflow using the main "Workflows" menu.

create-workflow.png

Workflows are a powerful way to connect things together. Sometimes these things are physical devices, but this case, the things are two disparate services: Slack and Discourse.

The high level of what this workflow will do is:

  1. Every minute, request the latest posts from the Discourse API.
  2. Look for the oldest post that is still newer than what we've already sent to Slack.
  3. Send the post to a Slack channel.
  4. Remember the ID of the last post we sent.
  5. Repeat.

All workflows start with some kind of trigger. In this example the trigger is a timer. Drag a timer node onto the workflow canvas and set the interval to once per minute.

timer-trigger.png

Next, drag an HTTP node onto the canvas and connect it to the timer node. We'll be using the HTTP node to request data from Discourse.

http-node.png

The HTTP node requires the URL to request and the location on the payload to put the response. Workflows use the concept of a payload that passes through each node as they execute. Workflow nodes can add, remove, and change properties on the payload as it passes through them. In this case, we're adding the response from the HTTP request to the data.latest property on the payload. This new field will then be available to all nodes that come after.

Before we keep going, let's add some debugging so we can make sure this request is happening correctly. Add a debug node and attach it to the HTTP node. Also add a virtual button trigger so we can manually invoke this workflow without waiting for the timer.

debug-node.png

Next, deploy the workflow using the "Deploy Workflow" button on the top right.

deploy-workflow.png

Once deployed, you can either wait a minute for the timer to trigger, or push the virtual button. Once you do, the debug tab will populate with the current workflow payload.

debug-output.png

As you can see, the response from the HTTP node has been placed on the data.latest field in the payload. The HTTP response will include three properties: body, headers, and status code. For this workflow, we only care about the body.

Now let's get the ID of the last post we sent to Slack. The first time this runs, we won't have a value, but we'll handle that case in a later node. Drag a get value node to the canvas and attach it to the http node.

get-value-node.png

The get value node, combined with a store value node that we'll add later, allows us to store information between workflow runs. The get value node requires two configuration options: the ID of the value to get and where on the payload to put it. In this example, the ID is lastId and the payload path is data.lastId. So what happens is this node will lookup the stored value with ID lastId and put the result on the payload at data.lastId. You can get/store any data you want with any ID you want using these nodes. Losant takes care of storing this information for you.

Now we have data from the Discourse API and the ID of the last post we sent to Slack. We can now add some logic to determine if there is a new post that we need to send. In order to do this, we'll have to use a function node. Drag a function node onto the canvas and connect it to the get value node.

function-node.png

I've also moved the debug node down to after the function node so we can see how it modifies the payload. Copy-paste the following as the function:

This code first checks if the lastId we got from the get value node is set to anything. If it's not, we're going to simply grab the most recent post and return. The next section attempts to find the oldest post that is still newer than the lastId we stored. The last line just makes sure we actually found a new post. If we didn't, it simply sets the resulting post ID to the value stored in lastId.

The function node has the ability to alter the payload in two ways. You can either change/add/remove properties on the payload object, or you can return an entirely new payload. In this example, we are returning a new object, which means the workflow payload, from this point on, will be set to what is returned. I chose to do this because I wanted a cleaner payload for the rest of this workflow. I no longer needed the response from the HTTP node or any other data that has been added - all I need is the new post ID and the new post itself.

Feel free to deploy and test the workflow again. Since this is the first time this has exectuted, your payload should look something like:

{
  "newPost": null,
  "newPostId": 162
}

 Now let's store the last ID using a store value node. Drag a store value node onto the canvas and connect it to the function node.

store-value-node.png

The store value node works similarly to the get value node, except in reverse. The first configuration parameter is the ID of the value, which is set to lastId. The second parameter is the location on the payload where the value exists. Since the function node entirely replaced the payload with { "newPostId" : #, "newPost": { ... }}, the payload path is simply newPostId since that value is directly on the root of the payload.

The last step is to check if a new post exists and send it to Slack. Add a conditional node and attach it to the store value node.

condition-node.png

The condition node allows us to write conditional expressions using template variables to access values from the payload. Using the debug node, you can see the payload currently has newPost and newPostId fields. This condition node is checking that the newPost field is not null, which means there is a new post that we need to send to slack.

{{ newPost }} !== null

The last node we need to add is the Slack node. Drag a Slack node onto the canvas and connect it to the true output (green) on the conditional node.

slack-node-2.png

Losant makes use of Slack's built-in webhook support. The first thing you have to do is create a new webhook integration for your Slack account. Once that's done, paste the webhook URL provided by Slack into the URL configuration field.

The second configuration field is the Slack room to post the message into. This example posts in the #support room, but you can choose whatever makes sense for you.

The last configuration field is the message to post. This field supports templates so you can pull values from the payload and display them anyway you'd like. For this example, I've used the following template:

New Discourse Message
Date: {{ newPost.created_at}}
User: {{ newPost.name }} ({{ newPost.username }})
{{ newPost.raw }}
Post Link: https://forums.losant.io/t/{{ newPost.topic_slug }}/{{ newPost.topic_id }}/{{ newPost.post_number }}
Topic Link: https://forums.losant.io/t/{{ newPost.topic_slug }}/{{ newPost.topic_id }}

There is a lot of information available on Discourse posts, so feel free to change this message to include the information most important to you.

This workflow is now done! Deploy it using the top right deploy button and now whenever a user posts a message in Discourse it will appear in your Slack room.

Here's what the final workflow looks like:

workflow-complete.png

This workflow provide basic Discourse to Slack integration, but it's also an example for integrating any number of existing services. Losant workflows are a powerful way to connect the unconnected to build larger solutions out of smaller puzzle pieces.

If you have any questions or comments, feel free to leave them below.

Brandon Cannaday

Brandon is passionate about developer tools and user experience. He has over 10 years of enterprise software experience in the chemical detection, telecommunications, and cloud platform industries.