Build a WhatsApp chatbot with Ruby, Sinatra and Twilio
Tags: ruby, sinatra, whatsapp, twilio
Published: 2020-02-17 12:22:14

---
title: Build a WhatsApp chatbot with Ruby, Sinatra and Twilio
published: true
description: Find out how easy it is to build a chatbot for WhatsApp using the Twilio API for WhatsApp and the Ruby web framework Sinatra.
tags: ruby, sinatra, whatsapp, twilio
canonical_url: https://www.twilio.com/blog/whatsapp-chatbot-ruby-sinatra-twilio
cover_image: https://twilio-cms-prod.s3.amazonaws.com/images/ruby-whatsapp-header.width-808.png
---

Chatbots are programs that communicate some way with humans. They can be very basic, responding to keywords or phrases, or use something like [Twilio Autopilot](https://www.twilio.com/docs/autopilot) to take advantage of natural language understanding (NLU) to provide a richer experience and build out more complicated conversations.

In this tutorial we are going to see how easy it is to get started building chatbots for WhatsApp using the [Twilio API for WhatsApp](https://www.twilio.com/docs/sms/whatsapp/api) and the Ruby web framework [Sinatra](http://sinatrarb.com/). Here's an example of the conversation we're going to build:

![Using the bot inside the WhatsApp application. First we ask for cats and get a cat picture and fact. Then we ask for dogs and get a dog picture and fact. Finally we ask the bot if it can do anything else and it says it only knows about dogs and cats.](https://twilio-cms-prod.s3.amazonaws.com/original_images/kR2z6AULs2kNSQ7bnRi3EnSrQOl6IrgO0Ei4JJlUdbTIKCI_263WUusPoxP59mYG7fr_B3wPtMNKqV)

## What you'll need

To build your own WhatsApp bot along with this tutorial, you will need the following:

*   [Ruby](https://www.ruby-lang.org/en/downloads/) and [Bundler](https://bundler.io/) installed
*   [ngrok](https://ngrok.com/) so we can[ ](https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html)[expose our local webhook endpoints to the world in style](https://www.twilio.com/blog/2015/09/6-awesome-reasons-to-use-ngrok-when-testing-webhooks.html) 
*   A [WhatsApp account](https://www.whatsapp.com/)
*   A Twilio account (if you don't have one, [sign up for a new Twilio account here and receive $10 credit when you upgrade](https://twil.io/philnash))

## Configure your WhatsApp sandbox

To launch a bot on WhatsApp you must [go through an approval process with WhatsApp](https://www.twilio.com/whatsapp/request-access), but Twilio allows you to [build and test your WhatsApp apps using our sandbox](https://www.twilio.com/console/sms/whatsapp/learn). Let's start by configuring the sandbox to use with your WhatsApp account.

The Twilio console walks you through the process, but here's what you need to do:

1.  Head to the _WhatsApp sandbox_[ area of the Twilio console](https://www.twilio.com/console/sms/whatsapp/learn), or navigate from _[the console](https://www.twilio.com/console/)_ to _[Programmable SMS](https://www.twilio.com/console/sms)_ and then _[WhatsApp](https://www.twilio.com/console/sms/whatsapp/learn)_
2.  The page will have the WhatsApp sandbox number on it. Open your WhatsApp application and start a new message to that number
3.  The page also has the message you need to send, which is "join" plus two random words, like "join flagrant-pigeon". Send your message to the sandbox number

![The Twilio Sandbox for WhatsApp first screen. It tells you to send a message to the WhatsApp number +1 415 523 8886 with a code that starts with "join".](https://twilio-cms-prod.s3.amazonaws.com/images/-2RH88czY--Dk8rDF3soLLXcu_wOI9fg4Y202EY_CI5CZE.width-500.png)

When you receive a message back, you are all set up and ready to work with the sandbox.

## Creating the Ruby application

Let's kick off a new Ruby application in which to build our bot. Start by creating a new directory to work in. Then initialise a new `Gemfile` in the app and create a couple of files we'll need:

```bash
mkdir whatsapp-bot
cd whatsapp-bot
bundle init
mkdir config
touch bot.rb config.ru config/env.yml
```

Add the gems we will use to build this application:

*   [Sinatra](http://sinatrarb.com/), a simple web framework
*   The[ twilio-ruby gem](https://github.com/twilio/twilio-ruby) so we can generate [TwiML](https://www.twilio.com/docs/sms/twiml)
*   [http.rb](https://github.com/httprb/http) to help us make some HTTP requests later
*   [Envyable](https://github.com/philnash/envyable) to[ manage environment variables in the application](https://www.twilio.com/blog/2015/02/managing-development-environment-variables-across-multiple-ruby-applications.html)

```bash
bundle add sinatra twilio-ruby http envyable
```

`config/env.yml` will store our application config and Envyable will load it into the environment for us. We only need to store one piece of config for this application: your Twilio auth token which you can find on your [Twilio console dashboard](https://www.twilio.com/console). Add your auth token to `config/env.yml`:

```yaml
TWILIO_AUTH_TOKEN: YOUR_TWILIO_AUTH_TOKEN
```

We'll use `config.ru` to load the application and the config, and to run it. Copy the following into `config.ru`:

```ruby
require 'bundler'
Bundler.require

Envyable.load('./config/env.yml')

require './bot.rb'
run WhatsAppBot
```

Let's test that everything is working as expected by creating a "Hello World!" Sinatra application. Open `bot.rb` and enter the following code:

```ruby
require "sinatra/base"

class WhatsAppBot < Sinatra::Base
  get '/' do
    "Hello World!"
  end
end
```

On the command line start the application with:

```bash
bundle exec rackup config.ru
```

The application will start on [localhost:9292](http://localhost:9292). Open that in your browser and you will see the text "Hello World!".

## Building a chatbot

Now that our application is all set up we can start to build our bot. In this post we will build a simple bot that responds to two keywords when someone sends a message to our WhatsApp number. The words we're going to look for in the message are "dog" or "cat" and our bot will respond with a random picture and fact about either dogs or cats.

### Webhooks

With the Twilio API for WhatsApp, when your number (or sandbox account) receives a message, Twilio makes a [webhook request](https://www.twilio.com/docs/usage/webhooks) to a URL that you define. That request will include all the information about the message, including the body of the message.

Our application will need to define a route that we can set as the webhook request URL to receive those incoming messages, parse out whether the message contains the words we are looking for, and respond with the use of [TwiML](https://www.twilio.com/docs/sms/twiml). TwiML is a set of XML elements that describe how your application communicates with Twilio.

The application we have built so far could respond to a webhook at the root path, but all it does is respond with "Hello World!" so let's get to work updating that.

Let's remove the "Hello World!" route and add a `/bot` route instead. Twilio webhooks are `POST` requests by default, so we'll set up the route to handle that too. To do so, we pass a block to the `post` method that Sinatra defines.

```ruby
require "sinatra/base"

class WhatsAppBot < Sinatra::Base
  post '/bot' do

  end
end
```

Next let's extract the body of the message from the request parameters. Since we're going to try to match the contents of the message against the words "dog" and "cat" we'll also translate the body to lower case.

```ruby
class WhatsAppBot < Sinatra::Base
  post '/bot' do
    body = params["Body"].downcase
  end
end
```

We are going to respond to the message using TwiML and the `twilio-ruby` library gives us a useful class for building up our response: `Twilio::TwiML::MessagingResponse`. Initialise a new response on the next line:

```ruby
class WhatsAppBot < Sinatra::Base
  post '/bot' do
    body = params["Body"].downcase
    response = Twilio::TwiML::MessagingResponse.new
  end
end
```

The `MessagingResponse` object uses the builder pattern to generate the response. We're going to build a message and then add a body and media to it. We can pass a block to the `Twilio::TwiML::MessagingResponse#message` method and that will nest those elements within a [`<Message>` element](https://www.twilio.com/docs/sms/twiml/message) in the result.

```ruby
class WhatsAppBot < Sinatra::Base
  post '/bot' do
    body = params["Body"].downcase
    response = Twilio::TwiML::MessagingResponse.new
    response.message do |message|
      # nested in a <Message>
    end
  end
end
```

Now we need to start building our actual response. We'll check to see if the body includes the word "dog" or "cat" and add the relevant responses. If the body of the message contains neither word we should also add a default response to tell the user what the bot can respond to.

```ruby
class WhatsAppBot < Sinatra::Base
  post '/bot' do
    body = params["Body"].downcase
    response = Twilio::TwiML::MessagingResponse.new
    response.message do |message|
      if body.include?("dog")
        # add dog fact and picture to the message
      end
      if body.include?("cat")
        # add cat fact and picture to the message
      end
      if !(body.include?("dog") || body.include?("cat"))
        message.body("I only know about dogs or cats, sorry!")
      end
    end
  end
end
```

We currently have no way to get dog or cat facts. Luckily there are some APIs we can use for this. For dogs we will use the [Dog CEO API for pictures](https://dog.ceo/dog-api/) and [this dog API for facts](https://kinduff.github.io/dog-api/). For cats there's [TheCatAPI for pictures](https://thecatapi.com/) and the [cat facts API for facts](https://catfact.ninja/). We'll use the http.rb library we installed earlier to make requests to each of these APIs.

Each API works with `GET` requests. To make a get request with http.rb you call `get` on the `HTTP` module passing the URL as a string. The `get` method returns a response object whose contents you can read by calling `to_s`.

To make the application nice and tidy let's wrap up the API calls to each of these services into a `Dog` and `Cat` module, each with a `fact` and `picture` method.

Add these modules to the bottom `bot.rb`:

```ruby
module Dog
  def self.fact
    response = HTTP.get("https://dog-api.kinduff.com/api/facts")
    JSON.parse(response.to_s)["facts"].first
  end

  def self.picture
    response = HTTP.get("https://dog.ceo/api/breeds/image/random")
    JSON.parse(response.to_s)["message"]
  end
end

module Cat
  def self.fact
    response = HTTP.get("https://catfact.ninja/fact")
    JSON.parse(response.to_s)["fact"]
  end

  def self.picture
    response = HTTP.get("https://api.thecatapi.com/v1/images/search")
    JSON.parse(response.to_s).first["url"]
  end
end
```

Now we can use these modules in the webhook response like so:

```ruby
class WhatsAppBot < Sinatra::Base
  post '/bot' do
    body = params["Body"].downcase
    response = Twilio::TwiML::MessagingResponse.new
    response.message do |message|
      if body.include?("dog")
        message.body(Dog.fact)
        message.media(Dog.picture)
      end
      if body.include?("cat")
        message.body(Cat.fact)
        message.media(Cat.picture)
      end
      if !(body.include?("dog") || body.include?("cat"))
        message.body("I only know about dogs or cats, sorry!")
      end
    end
  end
end
```

To return the message back to WhatsApp via Twilio we need to set the content type of the response to "text/xml" and return the XML string.

```ruby
class WhatsAppBot < Sinatra::Base
  post '/bot' do
    body = params["Body"].downcase
    response = Twilio::TwiML::MessagingResponse.new
    response.message do |message|
      if body.include?("dog")
        message.body(Dog.fact)
        message.media(Dog.picture)
      end
      if body.include?("cat")
        message.body(Cat.fact)
        message.media(Cat.picture)
      end
      if !(body.include?("dog") || body.include?("cat"))
        message.body("I only know about dogs or cats, sorry!")
      end
    end
    content_type "text/xml"
    response.to_xml
  end
end
```

That's all the code for the webhook, but there's one more thing to consider.

### Webhook security

This might not be the most mission critical data to be returning in a webhook request, but it is good practice to secure your webhooks to ensure you only respond to requests from the service you are expecting. [Twilio signs all webhook requests using your auth token](https://www.twilio.com/docs/usage/webhooks/webhooks-security#validating-signatures-from-twilio) and you can validate that signature to validate the request.

The [`twilio-ruby` library provides rack middleware to make validating requests from Twilio](https://www.twilio.com/blog/2014/09/securing-your-ruby-webhooks-with-rack-middleware.html) easy: let's add that to the application too. At the top of your `WhatsAppBot` class include the middleware with the `use` method. Pass the following three arguments to `use`: the middleware class `Rack::TwilioWebhookAuthentication`, the auth token, and the path to protect (in this case, "/bot".)

```ruby
class WhatsAppBot < Sinatra::Base
  use Rack::TwilioWebhookAuthentication, ENV['TWILIO_AUTH_TOKEN'], '/bot'
 
  post '/bot' do
```

## Hooking up the bot to WhatsApp

On the command line stop the application with `ctrl/cmd + c` and restart it with:

```bash
bundle exec rackup config.ru
```

We now need to make sure Twilio webhooks can reach our application. This is why I included [ngrok](https://ngrok.com) in the requirements for this application. ngrok allows us to connect a public URL to the application running on our machine. If you don't already have ngrok installed, follow [the instructions to download and install ngrok](https://ngrok.com/download).

Start ngrok up to tunnel through to port 9292 with the following command:

```ruby
ngrok http 9292
```

This will give you an ngrok URL that you can now add to your WhatsApp sandbox so that incoming messages will be directed to your application.

![The ngrok dashboard. It shows the URL you can now use to tunnel traffic through to your locally running app.](https://twilio-cms-prod.s3.amazonaws.com/images/ljmvxF3qa5qGoqE3pu9Nb0ZRL_YadEM0YD8gMRkk09y_-b.width-500.png)

Take that ngrok URL and add the path to the bot so it looks like this: `https://YOUR_NGROK_SUBDOMAIN.ngrok.io/bot`. Enter that URL in the [WhatsApp sandbox admin](https://www.twilio.com/console/sms/whatsapp/sandbox) in the input marked "When a message comes in" and save the configuration.

![The Twilio Sandbox for WhatsApp config page. You should add your ngrok URL into the field labelled "When a message comes in".](https://twilio-cms-prod.s3.amazonaws.com/images/Kcu3CrbaWGwNd5-hNRFXG7D_wfu_hTxq55DGouJmEpu3lf.width-500.png)

## Testing your bot

You can now send a message to the WhatsApp sandbox number and your application will swing into action to return you pictures and facts of dogs or cats.

![Using the bot inside the WhatsApp application. First we ask for cats and get a cat picture and fact. Then we ask for dogs and get a dog picture and fact. Finally we ask the bot if it can do anything else and it says it only knows about dogs and cats.](https://twilio-cms-prod.s3.amazonaws.com/original_images/kR2z6AULs2kNSQ7bnRi3EnSrQOl6IrgO0Ei4JJlUdbTIKCI_263WUusPoxP59mYG7fr_B3wPtMNKqV)

## Build more bots

In this post we've seen how to configure the Twilio API for WhatsApp and connect it up to a Ruby application to return pictures and facts of dogs or cats. You can get all the [code for this bot on GitHub](https://github.com/philnash/ruby-whatsapp-bots).

It's a simple bot, but provides a good basis to build more. You could look into [receiving images from WhatsApp to make a visual bot](https://www.twilio.com/blog/celebrity-spotting-twilio-api-whatsapp-aws-rekognition-ruby) or [sending or receiving location as part of the message](https://www.twilio.com/docs/sms/whatsapp/api#location-messages-with-whatsapp). We could also build on this to create even smarter bots using Twilio Autopilot.

Have you built any interesting bots? What other features would you like to see explored? Let me know in the comments or on [Twitter at @philnash](https://twitter.com/philnash).