Tutorial

Inspect incoming webhooks with Phoenix LiveView

At some point, almost every web application needs to receive web hooks from other applications. And a web hooks is basically some sort of automated web request that sends data to you app. For example, if you have Stripe subscription app, Stripe will send web hooks to your app so it can notify when a subscription is renewed or canceled and you can take action accordingly.

However, debugging and inspecting these asynchronous HTTP callbacks can be a tricky task for developers. Traditional methods often fall short in providing real-time insights into the payloads being transferred, making the debugging process tedious and time-consuming.

Phoenix LiveView, has the potential to make this pretty easy. The gist is that a LiveView can subscribe to incoming webhooks from a controller and display the payload on a page for debugging purpose.

Crafting the LiveView webhook inspector

The heart of our webhook inspection tool is a LiveView page designed to capture and display webhook data as it arrives, in real-time.

Setting up the LiveView page

We begin by adding a new LiveView page, which will be responsible for managing incoming webhook data and dynamically updating the user interface.

lib/tutorialweb/live/webhookinspector_live.ex:

defmodule TutorialWeb.WebhookInspectorLive do
  use TutorialWeb, :live_view
  use Phoenix.PubSub

  def mount(_params, _session, socket) do
    Phoenix.PubSub.subscribe(Tutorial.PubSub, "webhooks")
    {:ok, assign(socket, :webhooks, [])}
  end

  def handle_info({:webhook_received, webhook_data}, socket) do
    webhooks = [{webhook_data, System.system_time(:millisecond)} | socket.assigns.webhooks]
    {:ok, assign(socket, :webhooks, webhooks)}
  end
end

In this code snippet, we subscribe to a PubSub topic named "webhooks" during the component's mount phase.

Note that the string webhooks, are just a picked adhoc and could be any string that is unique and makes sense.

This setup ensures that our LiveView module listens for :webhook_received events, updating the UI with the latest webhook data and a timestamp for each event, thereby providing a real-time feed of incoming webhooks.

Adding the LiveView Template

The next step involves creating a visually appealing and functional template for our LiveView component, ensuring the webhook data is presented in an accessible manner.

lib/tutorialweb/templates/webhookinspector_live/index.html.heex:

<div>
  <h1>Received Webhooks</h1>
  <ul>
    <%= for {webhook, timestamp} <- @webhooks do %>
      <li><%= timestamp %>: <%= webhook %></li>
    <% end %>
  </ul>
</div>

This template gracefully lists the received webhooks, displaying each piece of data alongside its corresponding timestamp, thus allowing developers to track the sequence and content of incoming webhooks with ease.

Implementing the Webhook receiver endpoint

To capture webhook data, we establish an endpoint within our Phoenix application, tasked with receiving and processing webhook payloads.

lib/tutorialweb/controllers/webhookcontroller.ex:

defmodule TutorialWeb.WebhookController do
  use TutorialWeb, :controller

  def receive(conn, "_params" = params) do
    Tutorial.PubSub.broadcast("webhooks", {:webhook_received, params})
    send_resp(conn, 200, "Webhook Received")
  end
end

This controller's receive action plays a crucial role, broadcasting the incoming webhook data to the "webhooks" PubSub topic. This seamless integration enables our LiveView component to instantly react to new webhooks, enriching the development experience with real-time data visualization.

Routing Configuration

The final piece of the puzzle involves configuring the necessary routes to facilitate the reception of webhooks and their display within our LiveView interface.

lib/tutorial_web/router.ex:

scope "/api", TutorialWeb do
  pipe_through :api

  post "/webhooks", WebhookController, :receive
end

# Add this to where the other live routes are
live "/webhooks", WebhookInspectorLive

This setup introduces a dedicated POST route at /api/webhooks for webhook reception, alongside a LiveView route at /webhooks for displaying the captured data in real-time.

Conclusion

The purpose of this tutorial was to show you how easy it is to do something that are pretty complex in other programming languages. Going forward, you probably want to add this in a admin page. Also, you can set up some storage in Ecto so the payloads are stored and searchable. That is for another tutorial though.