Publishing jokes in realtime using custom Webhooks and the Chuck Norris API

Ably Webhooks allow your servers to be notified or your server-less functions to be invoked via an HTTP request following channel lifecycle events (such as channel creation), presence events (such as members entering or leaving) or messages being published.

In this tutorial we will show you how to:

  • Set up a Webhook rule to publish messages from a channel to your web server
  • Create a simple Ruby on Rails web server to serve up the web app, issue authentication tokens for the Realtime browser client and process incoming webhook requests from Ably
  • Publish questions from a Realtime browser client to a channel, which are automatically delivered to your server via the webhook
  • Communicate with the Chuck Norris API to get the answer, and publish the answer as a message back to the client using the REST API
  • Subscribe to jokes published from Rails in the web app using a realtime pub/sub channel

The following diagram depicts the architecture for this tutorial:


Webhooks and Chuck Norris diagram

The diagram may look complicated, but fortunately getting the app up and running with Ably is not! Let’s get started.

P.S. If you want to skip ahead and try out the completed tutorial now, install it in one-click on Heroku for free:

Step 1 – Create your Ably app and API key

To follow this tutorial, you will need an Ably account. Sign up for a free account if you don’t already have one.

Access to the Ably global messaging platform requires an API key for authentication. API keys exist within the context of an Ably application and each application can have multiple API keys so that you can assign different capabilities and manage access to channels and queues.

You can either create a new application for this tutorial, or use an existing one.

To create a new application and generate an API key:

  1. Log in to your Ably account dashboard
  2. Click the “Create New App” button
  3. Give it a name and click “Create app”
  4. Copy your private API key and store it somewhere. You will need it for this tutorial.

To use an existing application and API key:

  1. Select an application from “Your apps” in the dashboard
  2. In the API keys tab, choose an API key to use for this tutorial. The default “Root” API key has full access to capabilities and channels.
  3. Copy the Root API key and store it somewhere. You will need it for this tutorial.

    Copy API Key screenshot

Step 2 – Install Rails

This tutorial is not intended to teach you how to develop using Rails and expect that you have some basic knowledge of Rails beforehand. If you are new to Rails, please the Getting Started with Rails guide.

To install Rails, make sure you have Ruby and RubGems installed, and run the following command to install the Rails gem:

gem install rails

To create a new Rails application for this tutorial, use the Rails generators to create a ready-to-use vanilla Rails application:

rails new webhook-demo --skip-active-record
cd webhook-demo

Please note that we included --skip-active-record so as to avoid an unnecessary dependency on a database for this tutorial. In most Rails applications, ActiveRecord is in fact needed so there would be no need to skip its installation.

Lets make sure Rails has been installed correctly by running bin/rails server and opening a browser window to http://localhost:3000/. You should see the Rails default information page.

See this step in Github

Step 3 – Set up a root route and add base HTML app

The Rails deafult information page is currently being served because there are no routes yet configured. Let’s add a route to config/routes.rb by adding the following:

Rails.application.routes.draw do
  root to: 'home#index'
end

The route is referencing the action index for the as yet undefined HomeController so let’s add that at app/controllers/home_controller.rb:

class HomeController < ApplicationController
  def index
  end
end

We are still missing a view for the index action, so let’s add a base view for the app at app/views/home/index.html.erb:

<h1>Chuck Norris Jokes</h1>

<div class="col">
  <h2>Search for joke</h2>
  <input type="text" id="text" placeholder="Enter text to find a joke or leave empty"></input>
  <button id="action-btn">Make me laugh Chuck!</button><span id="status"></span>
</div>
<div class="col">
  <h2>Chuck Norris Jokes</h2>
  <div id="output"></div>
</div>

And finally let’s add some JavaScript as a placeholder for our app at app/assets/javascripts/application.js:

$(function() {
  var $actionButton = $('#action-btn');
  $actionButton.on('click', function() {
    alert("Not yet implemented");
  });
});

Open a browser window to http://localhost:3000/ and confirm that you can see the new Chuck Norris base app page.

See this step in Github

Step 4 – Install Ably for Rails (server-side) and add auth route

To start using Ably within your Rails server, you first need to install the Ably gem. Generally with Rails applications, as they are synchronous, the REST library is preferred over the Realtime library. The Realtime library must be run within an EventMachine reactor which provides an asynchronous evented framework. For this tutorial, the REST only version of the Ably Rubygem is used as it requires less dependencies and is a perfect fit for this synchronous Rails application.

Add the following to your Gemfile

gem 'ably-rest', '~>1.1'

Then run the following command to install the required gems:

bundle install

Before we add the REST library to the Rails application, we need to add the private API key you copied in Step 1 to the Rails config/secrets.yml. Add the following to the development and production sections of your config/secrets.yml file:

development:
  ably_api_key: [INSERT YOUR API KEY HERE]

production:
  ably_api_key: [INSERT YOUR API KEY HERE]

Please note that whilst we have simplified the process for the purposes of this tutorial, we would recommend that you consider the twelve-factor methodology when configuring secrets – instead of adding the secrets to the configuration file, use environment variables by referencing them i.e. ably_api_key: <%= ENV['ABLY_API_KEY'] %>.

Let’s now add the stateless Ruby REST library as an instance variable on the ApplicationController so that it’s available to all controllers. Add the following to app/controllers/application_controller.rb

def ably_rest
  @ably_rest ||= Ably::Rest.new(key: Rails.application.secrets.ably_api_key)
end

Now we want to issue token requests for Realtime clients that want to authenticate with Ably. The Rails application has an API key that it can use to sign these token requests, and the Rails application can serve up these token requests through a new route. Add the following route to config/routes.rb:

get '/auth' => 'auth#issue_token_request'

That route is referencing the issue_token_request action of the as yet undefined AuthController, so let’s create app/controllers/auth_controller.rb now:

class AuthController < ApplicationController
  def issue_token_request
    render json: ably_rest.auth.create_token_request
  end
end

Try visiting http://localhost:3000/auth and you should see a JSON token request in the form:

{
  "keyName": "I2ACJQ.Lv8AUQ",
  "ttl": 3600000,
  "timestamp": 1473894038255,
  "capability": "{\"*\":[\"*\"]}",
  "nonce": "f6799a8e7fa6f77b8e1dac55314789b1",
  "mac": "35nifY9SRZ8KRDfKOPIS1qYWGP16r2lD59zJo9TH8pA="
}

We now have a web server running with an authentication endpoint. We are ready to setup the Ably Realtime client that uses token auth.

See this step in Github

Step 5 – Plug in Ably client-side and update UI

As we’ve just set up the auth endpont to support the token authentication scheme, we will proceed to set up a Realtime client library that will request a token immediately from the web server auth endpoint before authenticating with Ably.

First we need to ensure the Ably client library is loaded from our CDN. Add this script tag to app/views/layouts/application.html.erb:

<head>
  <script src="//cdn.ably.com/lib/ably.min-1.js" crossorigin="anonymous"></script>
</head>

Then add the following to your application code at app/assets/javascripts/application.js:

var ably = new Ably.Realtime({ authUrl: '/auth' });
ably.connection.on('connecting', function() { showStatus('Connecting to Ably...'); });
ably.connection.on('connected', function() { clearStatus(); });
ably.connection.on('disconnected', function() { showStatus('Disconnected from Ably...'); });
ably.connection.on('suspended', function() { showStatus('Disconnected from Ably for a while...'); });

var $status = $('#status');
function showStatus(text) {
  $status.text(text).show();
}
function clearStatus() {
  $status.hide();
}

The Ably Realtime client will now automatically obtain a token request from the provided authUrl and then connect to Ably using that token request for authentication. At this point, visit http://localhost:3000/ and you should see the status element change to “Connecting to Ably…” whilst a connection is established, and the status become empty once the connection is established.

Now let’s hook up the publishing of the Chuck Norris search text to an Ably channel within application.js:

var publishChannel = ably.channels.get('chuck:ask');
var $actionButton = $('#action-btn');
var $text = $('#text');

$actionButton.on('click', () => {
  /* Publish search text to the Ably channel so that a webhook is triggered */
  publishChannel.publish('text', $text.val(), function(err) {
    if (err) {
      return showStatus('Failed to publish to Chuck!');
    }
  });
  $text.val(''); /* clear search for next query */
});

And add a subscriber that listens for new Chuck Norris jokes within application.js:

var jokeChannel = ably.channels.get('chuck:jokes');
var $output = $('#output');

jokeChannel.subscribe(function(message) {
  var $joke = $('<div class="joke">').text(message.data.joke);
  $output.prepend($('<div>').append($joke));
});

At this point the web app will instance an Ably Realtime library and connect to Ably, will publish messages to the chuck:ask channel when requested, and will automatically subscribe to answers from the chuck:jokes channel. However the webhook is not in place yet, so at this point publishing a request for a joke won’t do anything.

Finally, add this styling CSS to @app/assets/stylesheets/application.css so that the app’s aesthetic is addressed.

See this complete step in Github

Step 6 – Add a webhook endpoint and configure the webhook

Before we configure a webhook, the Rails application needs to respond to webhook requests. Let’s set up a route, and then add the controller after. Add this to config/routes.rb:

post '/webhook/chuck' => 'webhook#chuck_request', format: :json

Notice that all webhook requests are delivered using the POST HTTP verb, and based on the encoding we choose when configuring the webhook, will be delivered with a JSON mimetype encoding.

Let’s create the webhook controller and action in app/controllers/webhook_controller.rb:

class WebhookController < ApplicationController
  # Incoming webhooks don't come from this web application so will not have an
  # authentication token from this web application
  skip_before_action :verify_authenticity_token

  def chuck_request
    webhook_envelope = JSON.parse(request.body.read)

    # Webhook messages are sent in items.data.messages, see https://goo.gl/WT9DnH
    if webhook_envelope["items"] && webhook_envelope["items"].kind_of?(Array)
      webhook_envelope["items"].each do |item|
        Array(item.dig('data', 'messages')).each do |message_raw|
          # Use Ably to decode messages from JSON so all decoding issues are handled by Ably
          message = Ably::Models::Message.from_json(message_raw)
          puts "Received Message with data '#{message.data}'"
        end
      end
      # Must respond with 20x response else the webhook request will be retried
      render json: { "success": true }
    else
      render json: { "error": "items Array attribute missing from body" },
             status: :unprocessable_entity
    end
  end
end

Now that the Rails application is ready to receive webhook requests, you can set up a webhook rule. However, in most cases, when running a Rails application locally (like we are doing now for this tutorial), the Rails app will not have a publicly addressable hostname and port. As such, you either have to deploy your Rails app to a web server, or alternatively you can use a tunneling service to your localhost like Ngrok. We have used Ngrok extensively and would highly recommend you consider it. For this tutorial, we assume you have installed ngrok.

Run ngrok to obtain a public endpoint for your local Rails server as follows:

ngrok http 3000
ngrok by @inconshreveable
Tunnel Status                 online
Update                        update available (version 2.0.24, Ctrl-U to update)
Version                       2.0.19/2.1.18
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://f1a38682.ngrok.io -> localhost:3000
Forwarding                    https://f1a38682.ngrok.io -> localhost:3000

Keep the ngrok process running, and visit the secure (https) Forwarding address provided to you in your browser. In this case it’s https://f1a38682.ngrok.io. Confirm that you can see the Chuck Norris home page which in turn means you have a public endpoint for the webhook you are about to set up.

To set up a webhook for this endpoint, follow these steps:

  1. Log into your app dashboard
  2. Create a new app by selecting “Create New App”, or select an existing one that you wish to use
  3. Click the “Integrations” tab
  4. Click on “New Integration Rule”
  5. Click “Choose” under “Webhooks”. When prompted, enter the name Ngrok endpoint + the Webhook route configured previously such as https://f1a38682.ngrok.io/webhook/chuck, choose Message as your source, and ensure that the channel filter ^chuck:ask is included to ensure only chuck joke requests are forwarded to your web server. See screenshot example below:


Setting up a Webhook

Once the webhook is saved, you are now ready to test that webhook is working. Visit http://localhost:3000/ in your browser, enter some text in the text field, and click the “Make me laugh Chuck!” button. You should within a second see your Rails server report an incoming request in the console log along with the message “Received Message with data ‘…’”. If you are having problems with the webhook setup, please see our recommendations for debugging Webhooks.

See this step in Github

Step 7 – Integrate with Chuck API and publish jokes over REST

Now that the publishing, subscribing, authentication and webhooks are in place, it’s time to hook the webhook controller into the awesome Chuck Norris API. Modify app/controllers/webhook_controller.rb by adding logic to communicate with the API:

def get_random_joke
  response = JSON.parse(HTTP.get('https://api.chucknorris.io/jokes/random').to_s)
  publish_joke response['value']
end

def find_matching_jokes(text)
  url = "https://api.chucknorris.io/jokes/search?query=#{CGI::escape(text)}"
  response = JSON.parse(HTTP.get(url).to_s)
  if response['result']
    response['result'].first(3).each do |joke|
      publish_joke joke['value']
    end
  else
    publish_joke "No Chuck joke matching text '#{text}'"
  end
end

def publish_joke(joke)
  ably_rest.channels.get('chuck:jokes').publish('joke', { joke: joke })
end

Then replace the message processing in the same controller with:

Array(item.dig('data', 'messages')).each do |message_raw|
  message = Ably::Models::Message.from_json(message_raw)
  if message.data.strip.empty?
    get_random_joke
  else
    find_matching_jokes message.data
  end
end

Once again, visit http://localhost:3000/ in your browser. Either type a word or phrase to filter jokes on or go for a random joke and click “Make me laugh Chuck!” button. You should see a response from the Chuck Norris API appear within the time it takes for that API to provide a joke. Given it’s a Chuck Norris API, that should be quick right?

See this step in Github

We’ve covered a lot of ground in this tutorial. You’ve successfully covered off a lot of Ably functionality including token authentication, realtime pub/sub and Webhooks. We strongly recommend you review the next steps below for essential further reading to help you understand how to use Webhooks in more detail.

Try this tutorial in one click on Heroku

The simplest way to try out this demo is to just install it now on Heroku for free. When installing with Heroku, you will automatically be provisioned with a free Ably account and the steps you need to take to configure the Webhook rules will be shown to you after the Heroku app is provisioned.

Download tutorial source code

git clone https://github.com/ably/tutorials.git

Checkout the tutorial branch:

git checkout webhook-chuck-norris-ruby-rails

And then run the demo locally by adding your Ably API key to config/secrets.yml, configuring the Webhook rules as described in this tutorial and running the demo rails server

Next steps

1. Find out more about Webhooks
2. Find out more about Ably Queues
3. Find out more about Ably Integrations
4. Learn more about other Ably features by stepping through our other Ably tutorials
5. Gain a good technical overview of how the Ably realtime platform works
6. Get in touch if you need help