dnlgrv

Building a bot for Facebook Messenger, using Elixir

This post will go over a practical implementation of writing a bot for Messenger, using Elixir.

The bot we’re going to write is called Bastion, a hero from Blizzard’s new game Overwatch. Bastion is a simple machine with a limited set of responses.

If Bastion receives any text message, he’ll reply with a message containing some help text and options for the user to select:

  1. Your name
  2. Your website

This should provide enough scope to explain some of the basics about creating a bot for Messenger, and some of the different message types.

Setting up the Facebook app

As we’re building a bot on top of Messenger, we need to do some setup at the Facebook side of things.

Create your app

Head over to Messenger and click Start Building. You’ll need to sign in to a Facebook account in order to access it.

In the top-right corner, click My Apps > Add a New App. You want to select a Website app, and fill in the rest of the details (such as name and contact email address).

Add a new app

You can skip quick start as you don’t need any of those steps. Towards the bottom of the left-hand menu, choose Messenger, and click Get Started.

Create a page for your app

This step is straightforward. All you need to do is Create a Page, or you can use an existing page if you have one.

Verify the bot

Now we actually have to start writing some code. We’ll need a basic web server that can respond to a request which Facebook is going to make to verify the URL of our bot.

Start off a new mix application:

mix new bastion --sup

We’re going to be using cowboy and Plug for my web server and router. Add those to your mix.exs file as dependencies and run mix deps.get:

def application do
  [applications: [:logger, :cowboy, :plug],
   mod: {Bastion, []}]
end

defp deps do
  [{:cowboy, "~> 1.0"},
   {:plug, "~> 1.0"}]
end

We need a module that will create our server, as well as a module that will be our router.

The server module is fairly straight forward, and the port can be configured (for Heroku’s benefit):

defmodule Bastion.Server do
  @default_port 4000

  def start_link(nil), do: start_server(@default_port)
  def start_link(port), do: port |> String.to_integer() |> start_server()

  defp start_server(port) do
    Plug.Adapters.Cowboy.http Bastion.Router, [], port: port
  end
end

We also need to add this module as a worker to the application supervision tree, in lib/bastion.ex:

children = [
  worker(Bastion.Server, [System.get_env("PORT")])
]

You can see here we’re passing in the PORT environment variable, and with pattern matching the server module will handle it accordingly.

In the Bastion.Server module you’ll notice we used a Bastion.Router module. We’ll write that next.

For Facebook to verify our web server, we need to respond to a request at a specific endpoint. It can be whatever you like, but ours is going to be /webhook. In order to verify, you need to echo back the incoming hub.challenge query string. You should also check that the incoming request is from Facebook, which can be done by checking hub.verify_token. You can also customise the verify token, but for this example we’ll be using secret-token.

defmodule Bastion.Router do
  use Plug.Router

  plug Plug.Logger
  plug :match
  plug :dispatch

  get "/webhook" do
    conn = Plug.Conn.fetch_query_params(conn)

    if conn.params["hub.verify_token"] == "secret-token" do
      send_resp(conn, 200, conn.params["hub.challenge"])
    else
      send_resp(conn, 401, "Error, wrong validation token")
    end
  end

  match _ do
    send_resp(conn, 404, "404 - Page not found")
  end
end

There are a few quality of life additions here that aren’t required in order to get this working, such as Plug.Logger (which logs all requests to the terminal) and the match function towards the end of the router, which will catch any missed requests and return a 404.

With all that in place I’m going to deploy the bot to Heroku at https://bastion-bot.herokuapp.com, using heroku-buildpack-elixir.

Head back to your Messenger dashboard, and click on Setup Webhooks. You need to provide the URL to your endpoint, the verify token that you set up in your app (secret-token in this example), and just check all of the subscription fields.

Subscribe your app to the page

Our final step is to subscribe the app to the page we created. Subscribing means that any events generated by the page (such as messages) will be sent to our bot.

The Getting Started guide states that you need to send a cURL request to subscribe, but there’s actually a useful button within the Webhooks box. Just chose your page and click Subscribe.

If that doesn’t work (I’ve had cases where it doesn’t), then you’ll need to use the cURL method. To do that, at the top of the Messenger dashboard you can select your page and it will give you the Page Access Token. Run the following, replacing <page access token> with your own:

curl -ik -X POST "https://graph.facebook.com/v2.6/me/subscribed_apps?access_token=<page access token>"

You should see a {“success”: true} returned in the response.

Now we can start sending and receiving messages from our bot.

Our first message

When someone sends a message to our bot, we’re going to receive a POST to the same endpoint we used for verification. The payload will be in JSON so we’re going to have to decode it first.

To do that we’re going to need another dependency, and I’ll be using Poison. While we’re at it, lets grab HTTPoison too which we’ll need later on in order to send HTTP requests back to Facebook. Our updated dependencies in mix.exs looks like this:

def application do
  [applications: [:logger, :cowboy, :plug, :httpoison],
   mod: {Bastion, []}]
end

defp deps do
  [{:cowboy, "~> 1.0"},
   {:plug, "~> 1.0"},
   {:poison, "~> 2.1"},
   {:httpoison, "~> 0.8.3"}]
end

Don’t forget to run mix deps.get.

We need to create the POST route for our endpoint, parse the incoming JSON, then do something with the message. We’re going to delegate any incoming messages to a module called MessageHandler. That way our router isn’t doing too much work.

The incoming JSON payload is quite complex, and you can find the schema in the webhook reference documentation. I’m going to provide you with a simplified version of the code I use to parse incoming messages and call MessageHandler.handle/1 with each message:

post "/webhook" do
  {:ok, body, conn} = Plug.Conn.read_body(conn)

  body
  |> Poison.Parser.parse!(keys: :atoms)
  |> Map.get(:entry)
  |> hd()
  |> Map.get(:messaging)
  |> Enum.each(&Bastion.MessageHandler.handle/1)

  send_resp(conn, 200, "Message received")
end

Now we just need to do something with the message in the MessageHandler module. Through the magic of pattern matching, we’re going to have different implementations of MessageHandler.handle/1 depending on the incoming message. What we want to do first is match on any incoming text message, and send back a message with the buttons.

defmodule Bastion.MessageHandler do
  require Logger

  def handle(msg = %{message: %{text: _text}}) do
    # incoming text message
  end

  def handle(msg) do
    Logger.info "Unhandled message:\n#{inspect msg}"
  end
end

This is the outline of the module. We have a function that matches on any text messages (we don’t actually care what the text is though), and a fallback function that will log any incoming messages that we aren’t handling.

To send a message back to the user, we need to use the [Send API][14]. We’re going to use what is called the Button Template to send a message to the user with our buttons. You’re going to need your Page Access Token from the Messenger dashboard for this part:

@fb_page_access_token System.get_env("FB_PAGE_ACCESS_TOKEN")

def handle(msg = %{message: %{text: _text}}) do
  buttons = [
    %{type: "postback", title: "Your name", payload: "PB_NAME"},
    %{type: "web_url", title: "Your website", url: "https://playoverwatch.com/en-us/heroes/bastion/"}
  ]

  send_button_message(msg.sender.id, "Choose from the following options", buttons)
end

defp send_button_message(recipient, text, buttons) do
  payload = %{
    recipient: %{id: recipient},

    message: %{
      attachment: %{
        type: "template",
        payload: %{
          template_type: "button",
          text: text,
          buttons: buttons
        }
      }
    }
  }

  url = "https://graph.facebook.com/v2.6/me/messages?access_token=#{@fb_page_access_token}"
  headers = [{"Content-Type", "application/json"}]
  HTTPoison.post!(url, Poison.encode!(payload), headers)
end

If you’re using Heroku, make sure that you’ve set the environment variable FB_PAGE_ACCESS_TOKEN before deploying, and also added it to the list of exported variables in elixir_buildpack.config.

Now we can deploy our bot and try it out on the Facebook Page.

Handling the postback

So the message has gone to the user, and there are buttons they can click. Luckily for us, we don’t need to do anything for the Your website button, as Messenger handles that for us (web_url is a special type of button that will open the specified URL when pressed).

So we need to handle what happens when the user clicks the Your name button. When we sent the button to the user, we included a payload, which was PB_NAME. When the user clicks the button, Messenger will send us the button’s payload, which is how we’ll know which button was pressed.

For our MessageHandler this is easily done with pattern matching, like so:

def handle(msg = %{postback: %{payload: "PB_NAME"}}) do
  # incoming postback
end

In response to the button being sent, we’re going to send a simple text message back to the user with Bastion’s real name:

def handle(msg = %{postback: %{payload: "PB_NAME"}}) do
  send_text_message(
    msg.sender.id,
    "My name is SST Laboratories Siege Automation E54, otherwise known as Bastion"
  )
end

defp send_text_message(recipient, text) do
  payload = %{
    recipient: %{id: recipient},

    message: %{
      text: text
    }
  }

  url = "https://graph.facebook.com/v2.6/me/messages?access_token=#{@fb_page_access_token}"
  headers = [{"Content-Type", "application/json"}]
  HTTPoison.post!(url, Poison.encode!(payload), headers)
end

Once you click on the Your name button you should get your text message back from the bot.

That’s all folks

So there you have it, creating a bot for Messenger. From here you can look at the other types of messages you can send, or use this as a base to build your own bot.

I hope you found this post useful. If you did, please leave a comment.