Fivetrex.WebhookPlug (Fivetrex v0.2.1)

View Source

A Plug for handling incoming Fivetran webhooks in Phoenix/Bandit applications.

This plug verifies webhook signatures and parses the payload into a Fivetrex.Models.WebhookEvent struct, making it easy to integrate Fivetran webhooks into your Phoenix application.

Features

  • Verifies HMAC-SHA256 signatures to ensure requests are from Fivetran
  • Parses webhook payloads into typed structs
  • Returns appropriate HTTP error responses for invalid requests
  • Assigns the parsed event to the connection for downstream handlers

Installation

Step 1: Capture Raw Body

This plug requires access to the raw request body for signature verification. Add a body reader to your endpoint:

# In lib/my_app_web/endpoint.ex
plug Plug.Parsers,
  parsers: [:urlencoded, :multipart, :json],
  pass: ["*/*"],
  body_reader: {Fivetrex.WebhookPlug, :cache_raw_body, []},  # Add this
  json_decoder: Phoenix.json_library()

Step 2: Add Route

Add a route for the webhook endpoint:

# In lib/my_app_web/router.ex
scope "/webhooks", MyAppWeb do
  pipe_through :api

  post "/fivetran", FivetranWebhookController, :receive
end

Step 3: Use the Plug

Add the plug to your controller:

defmodule MyAppWeb.FivetranWebhookController do
  use MyAppWeb, :controller

  plug Fivetrex.WebhookPlug,
    secret: {MyApp.Config, :fivetran_webhook_secret, []}
    # Or: secret: "my_static_secret"
    # Or: secret: {:system, "FIVETRAN_WEBHOOK_SECRET"}

  def receive(conn, _params) do
    event = conn.assigns.fivetran_event

    case event.event do
      "sync_end" ->
        # Handle sync completion
        handle_sync_end(event)

      "sync_start" ->
        # Handle sync start
        handle_sync_start(event)
    end

    json(conn, %{status: "ok"})
  end
end

Configuration Options

  • :secret - Required. The webhook secret for signature verification. Can be provided as:

    • A string: secret: "my_secret"
    • A tuple for runtime fetching: secret: {Module, :function, args}
    • A system env tuple: secret: {:system, "ENV_VAR_NAME"}
  • :event_key - Optional. The key to use in conn.assigns for the parsed event. Defaults to :fivetran_event.

  • :on_error - Optional. A function to customize error responses. Signature: fn conn, error_type -> conn. Defaults to sending JSON errors.

Assigns

On successful verification, this plug adds:

  • conn.assigns.fivetran_event - The %Fivetrex.Models.WebhookEvent{} struct
  • conn.assigns.raw_body - The raw request body (for debugging)

Error Handling

Invalid requests receive appropriate HTTP responses:

  • 400 Bad Request - Missing signature header
  • 401 Unauthorized - Invalid signature
  • 422 Unprocessable Entity - Invalid JSON payload

See Also

Summary

Functions

Custom body reader that caches the raw body for signature verification.

Functions

cache_raw_body(conn, opts)

@spec cache_raw_body(
  Plug.Conn.t(),
  keyword()
) :: {:ok, binary(), Plug.Conn.t()} | {:more, binary(), Plug.Conn.t()}

Custom body reader that caches the raw body for signature verification.

Use this as the :body_reader option in Plug.Parsers:

plug Plug.Parsers,
  parsers: [:json],
  body_reader: {Fivetrex.WebhookPlug, :cache_raw_body, []},
  json_decoder: Jason