In this article we will develop a webhook listener application with sinatra. The application will consume bitbucket events, such as pushes and pull requests, and perform the deployment of another application. You can find the code in the github repository. You are more than welcome to contribute any improvements.
In recent months, in my company work, there has been a need to automate the deployment to the test environment. The context was the following. We had an application with rails managed through bitbucket. The application had development, test, pre-production and production environments. In the test environment, the application was hosted on an amazon instance running amazon linux 2 distribution with the services needed to run the application (mongo, nginx, puma, redis, sidekiq, …).
Until then, the deployment was completely manual: pushing to the test branch, connecting to the instance, updating the sources and restarting the services. So, we start looking for alternatives to automate the process. Among the alternatives considered, webhook proved to be very efficient to our context. In addition to its efficiency, its implementation is incredibly simple.
Webhook is a technique for an application to provide real-time information for other applications. Unlike a typical API, where you would need to frequently request information, webhook allows you to listen for events, receiving and processing information immediately as events are triggered.
Therefore, a webhook is known as a reverse API, because it provides an equivalent specification and you must implement one, which would be webhook listener application, to consume a webhook. The webhook will send an HTTP request to your application (usually a POST) and you will then be charged with interpreting it.
The bitbucket provides webhook to integrate its services to other applications. We use webhook to automate the deployment to the test environment. Instead of manually connecting to the instance, updating the sources and restarting the services, we want the webhook listener application to perform these processes automatically when pushing to the test branch.
There are different approaches available to consume a webhook. You can extend your application to handle events, you can create a microservice, or you can create a Function as a Service (FaaS). We choose to create a microservice.
Therefore, we developed a webhook listener application to consume bitbucket events and to automate the deployment. The figure below represents an overview of the steps involved.
According to the overview, when a user pushes to the application repository on bitbucket, the bitbucket webhook provider triggers an event to the webhook listener application, which receives the event and performs the deployment.
Okay, now that we have defined the application overview, let’s get our hands on code. We will develop the application with sinatra, a lightweight ruby framework that allows the development of applications with minimal effort. The framework is build upon rack, a specification that provides an interface between web servers and ruby applications.
We will start by creating the application directory.
$ mkdir bitbucket-webhook-listener
Next, as we want to run the application in production, and, as defined in the rack specification, we will create a Gemfile and a rackup file named config.ru in the application’s root directory.
# bitbucket-webhook-listener/Gemfilesource 'https://rubygems.org'gem 'json'
The Gemfile is used by the bundler gem, which is a dependency management tool, and it describes the gem dependencies of the application.
# bitbucket-webhook-listener/config.rurequire 'bundler/setup'
The config.ru file is a rackup file used to start the application. In this file, we require the bundler dependencies and run the application. However, as you can see, we require the application.rb file that we have not yet implemented, so let’s create it now.
In the application’s root directory, create the application.rb file.
# bitbucket-webhook-listener/application.rbrequire 'sinatra'
require 'sinatra/reloader'configure do
set :server, :puma
endget '/' do
json message: 'Hello World!'
In this file, the application requests are handled. In the first lines, we request the sinatra and the extensions that we will use. Next, we set puma as our web server and JSON as our content type. Then, we create a handler for the route GET / and return the message “Hello World”.
Ok, now let’s run the application. However, before that, we have to install the dependencies. We assume here that you have already installed ruby and bundler. To install the application dependencies and run the application, run the commands below in the application’s root directory.
$ bundle install
Now that the dependencies have been installed, run the application.
The rackup command is a rack command that runs the application through the config.ru file on port 9292. Open up your browser and go to http://localhost:9292. You should see the message “Hello World”.
Now that the application is running, let’s implement the route to consume bitbucket events. According to the bitbucket documentation, events are sent through a POST request in JSON. Therefore, add the following lines in the application.rb file.
# bitbucket-webhook-listener/application.rb...post '/' do
req = JSON.parse request.body.readrepo = req['repository']unless repo['type'] == 'repository' && repo['name'] == 'app'
endpush = req['push']['changes']['new']unless push['type'] == 'branch' && push['name'] == 'test'
end# Runs the deployment processreturn json message: 'The deployment process was run!'
Ok, now we have a POST / route to consume bitbucket events. However, we just want to consume pushes to a specific repository and to a specific branch, so we need to check the data. When the data matches the expected event, we perform the deployment.
Next, we need to check the new route. To do that, let’s send a POST request to see how the application responds. We will use postman here to send the request.
As you can see, the application responded with status 204 (no content). It responded that way because we did not send any parameters in the request. So, let’s send another request with a bitbucket event as parameter. We can use the following JSON as the request parameter to mock a push event to the test branch.
Now that we have defined the parameter, let’s send the new request.
Very well, the application responded with the message “The deployment process was run”, which means that the application consumed the event and performed the deployment.
The implementation is done. The next step is the deployment of the webhook listener. We hosted the webhook listener in the test environment, facilitating the deployment of the rails application, since the webhook listener and the rails application were in the same instance.
In this article, we will not cover the deployment of the webhook listener, because the deployment is specific to its context. If you are interested, in the github repository are available the files we use in production: the puma configuration (puma.rb), the puma service (deployment/puma.service) and the nginx configuration (deployment/nginx.conf).
A final step is to set up the bitbucket webhook provider to send events to the webhook listener application.
In the bitbucket repository, go to “Settings” / “Webhooks” / “Add webhook” and complete the form. Note that to add the webhook you must have the URL to send the events, so the webhook listener application should already be in production. Once you added the webhook, whenever an event is triggered, the webhook will send the event to the URL you defined.
In this article, the concept of webhook was addressed and a webhook listener application was developed. You are now ready to implement an application to consume bitbucket events and perform any operation.