Intercepting webhooks in a library. Put a plug in the Endpoint or a `use` macro in the Router?

So I’m writing a lib to wrap an API. The external API implements webhooks, which I want my lib to intercept and handle. Would it make more sense to put a plug in the projects endpoint, or a macro that creates a route in the router?

i.e.

endpoint.ex

plug MyLib.Webhooks, url: "/webhooks", handler: fn event -> IO.puts(event) end

OR
router.ex

use MyLib.Webhooks, url: "/webhooks", handler: fn event -> IO.puts(event) end

In the plug version I would match the route (what’s the best way to do this?), and then call the handler, then halt the conn

In the use version I would write a macro that creates a route based on the given webhook url.

Which one of these is better practice? Why?

1 Like

:wave:

I’d write a single plug that users put in their routers or anywhere else themselves. So I’d go with the first approach.

6 Likes

I would also put my vote on a Plug since that gonna be more flexible and less magical.

1 Like

If I use a plug, what’s the best way to determine if a incoming plug is one that should be handled or not? I can’t find a function in Phoenix.Router that says if a conn matches a route.

You can probably use conn.path_info and compare it with whatever is passed in opts:

defmodule YourPlug do
  # ...
  def call(conn, opts) do
    url = opts[:url] # "/webhooks" in your example
    case conn.path_info do
      [^url] -> # match
      _unmatched -> # no match
    end
  end
end

Ideally though, you shouldn’t care about this and leave it to the user, they would use forward macro when appropriate:

defmodule UsersRouter do
  # ...
  forward "/webhook", to: YourPlug
end

I’d look at :absinthe_plug for inspiration.

3 Likes

Using forward makes a lot more sense than what I was trying to do with the Plug taking a path and matching it against the Conn. Thank you!

2 Likes