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.