Webhook backend in ruby
Tags: ruby, rails
Published: 2024-01-03 06:42:19

I have heard of Webhook for a while now. But I never used it, and I have struggled a bit to understand what was really happening. 

And as always, I think trying to implement the thing is one of the best ways to understand what is happening. So, after reading one book or two about Webhooks and API. I thought it was the time to implement a simple Webhook API.

In this blog post, we will oversee all the non-functional and security parts. We will aim to understand how Webhooks are working and try to understand the spirit of it. But of course, these are important parts that need to be treated in production.

## Webhook

Webhook needs several things but let's try to put it simply. 
Of course we need two things : 
- A Webhook API provider
- A Webhook API consumer

The Webhook consumer needs to register with the provider and give a URL that can be requested when an event has happened. 

This URL is the callback URL, and it is an endpoint for the client to react to any change happening in the provider system. 

Then, we will need an Event history to be able to keep the client up to date on the event if any issue happens. 

Eventually, the Webhook consumer will need a callback endpoint to receive any updates. 

## Provider

Let's say we have a job that launches the washing for our laundry machine. We want to provide a way to make the client aware of when the machine will be ended without polling. 

We will get back to this job later for now we only need to know that it is called `WashingJob`.

First, as we said, the API Consumer needs to register with the API provider. So, the provider needs a Webhook subscription endpoint. 

Let's have a look at the WebHook subscription model. 
This will take a receiver_url (that will be query when the job is over), the customer_id, and the event that has happened that triggers our webhook (here the end of the washing machine).

```ruby
class WebHookSubController < ApplicationController
  def index
  end

  def new
  end

  def create
  
    WebHookSubscription.create(receiver_url: params[:receiver_url],topic: params[:topic], customer_id: params[:customer_id])
  end
end
```

The next step for us is to have an event history in the API provider to know if the clients have received the event well.
 
This will be needed in the case that API Consumer has not acknowledged the reception of the new event. So they can get the history and update every event that has not been received on their side. This can happen in different cases. Downtime of the API Receiver endpoint or server for example.

```ruby 
class CreateEventHistory < ActiveRecord::Migration[7.1]
  def change
    create_table :event_histories do |t|
      t.string :topic
      t.string :delivery_status
      t.string :customer_id

      t.timestamps
    end
  end
end
```

Now we can take a look to the washing job. This will be pretty simple. 

So, as you see below, we retrieved the customer subscription to this event. Then we create a row in our history so the customers can have a trace of the event, and, as we said before, be sure to be up to date on the history if there is any issue.

Then, we query the endpoint of the customer that is associated with this webhook subscription. If it is successful we update our event history to 'delivered' if not successful it is updated to 'not_delivered'.

What is important after that is to create an endpoint for the customer to query the event history. But we won't tackle that here. 

```ruby
require 'uri'
require 'net/http'

class WashingJob < ApplicationJob
  queue_as :default

  def perform(customer_id)
    
    
    sub = WebHookSub.find_by(topic: 'washing',customer_id: customer_id)
      ev = EventHistory.create!({topic: 'washing',customer_id: sub.customer_id, delivery_status: 'delivering'})
      created_at = ev.created_at
      uri = URI(sub.uri)
      res = Net::HTTP.get_response(uri)

      if res.is_a?(Net::HTTPSuccess)
        EventHistory.where(customer_id: sub.customer_id, topic: 'washing', delivery_status: 'delivering', created_at:).first.update(delivery_status: 'delivered')
      else
        EventHistory.where(customer_id: sub.customer_id, topic: 'washing', delivery_status: 'delivering', created_at:).first.update(delivery_status: 'not_delivered')
      end

  end
end

```


## Receiver

So, the customer who wants to know if the washing job is over must give an endpoint. We need another server, though, that has an endpoint.

```ruby
class WebhookEndpointController < ApplicationController
  def index 
    render :plain => "Event Accepted", :status => 202

  end
end
```

In this endpoint, we responded to the API provider that we received the event with no problem. We need to do that because the API provider keeps the Event History, as we discussed earlier. If we do not respond when we want to check the event history, we will see an inconsistent state, and this could be harmful to us.

## Conclusion

In this blog post, we have seen how to basically implement Webhook in Ruby. We have overlooked a lot of aspects of it. But we know that this needs work both from the API Provider side and the API Consumer side. 

Moreover, we need several endpoints to make Webhooks work. And give a great developer experience to API consumers.