HuggingfaceClient.Hub.WebhooksServer (huggingface_client v0.1.0)

Copy Markdown View Source

HuggingFace Webhook payload handling and signature verification.

Helps build webhook receivers that can verify payloads are genuinely from HuggingFace and parse structured webhook events.

See: https://huggingface.co/docs/hub/webhooks

Webhook payload structure

HuggingFace sends JSON POST requests with:

  • X-Webhook-Secret header (if configured)
  • A JSON body with event, repo, discussion, comment, updatedRefs, etc.

Example (with Plug/Phoenix)

defmodule MyAppWeb.WebhookController do
  use MyAppWeb, :controller

  def receive(conn, _params) do
    secret = Application.get_env(:my_app, :hf_webhook_secret)

    case HuggingfaceClient.Hub.WebhooksServer.verify_and_parse(conn.body_params,
           secret: secret,
           List.first(header_secret: get_req_header(conn, "x-webhook-secret"))
         ) do
      {:ok, event} ->
        handle_event(event)
        send_resp(conn, 200, "ok")

      {:error, reason} ->
        send_resp(conn, 400, reason)
    end
  end

  defp handle_event(%{"event" => %{"action" => "create", "scope" => "repo"}} = event) do
    IO.puts("Repo created: #{event["repo"]["name"]}")
  end

  defp handle_event(%{"event" => %{"scope" => "discussion"}} = event) do
    IO.puts("Discussion event: #{event["discussion"]["title"]}")
  end

  defp handle_event(_event), do: :ok
end

Summary

Functions

Returns true if the event is a comment on a discussion or PR.

Returns true if the event involves a discussion or pull request.

Parses a raw webhook payload into a structured event map.

Returns true if the event is a pull request (not a plain discussion).

Returns true if the event is a repository configuration change.

Returns true if the event is a repository content change (code push, file upload).

Returns the list of updated git branches from a repo content event.

Returns the list of updated tags from a repo content event.

Verifies a webhook payload's authenticity and parses it.

Verifies that the X-Webhook-Secret header matches the configured secret.

Functions

comment_event?(arg1)

@spec comment_event?(map()) :: boolean()

Returns true if the event is a comment on a discussion or PR.

discussion_event?(arg1)

@spec discussion_event?(map()) :: boolean()

Returns true if the event involves a discussion or pull request.

parse_event(payload)

@spec parse_event(map()) :: map()

Parses a raw webhook payload into a structured event map.

Event fields

  • "event" — map with "action" and "scope":
    • action: "create", "update", "delete", "move"
    • scope: "repo", "discussion", "comment", "repo.config", "repo.content"
  • "repo" — repository info (type, name, id, private, url)
  • "discussion" — present for discussion/PR events
  • "comment" — present for comment events
  • "updatedRefs" — list of updated git refs (for repo.content events)
  • "movedTo" — present for move events
  • "webUrl" — direct URL to the event location

Example

event = HuggingfaceClient.Hub.WebhooksServer.parse_event(raw_payload)

case event["event"]["scope"] do
  "repo"       -> handle_repo_event(event)
  "discussion" -> handle_discussion_event(event)
  "comment"    -> handle_comment_event(event)
  _            -> :ok
end

pull_request_event?(arg1)

@spec pull_request_event?(map()) :: boolean()

Returns true if the event is a pull request (not a plain discussion).

repo_config_event?(arg1)

@spec repo_config_event?(map()) :: boolean()

Returns true if the event is a repository configuration change.

repo_content_event?(arg1)

@spec repo_content_event?(map()) :: boolean()

Returns true if the event is a repository content change (code push, file upload).

updated_branches(arg1)

@spec updated_branches(map()) :: [String.t()]

Returns the list of updated git branches from a repo content event.

Example

branches = HuggingfaceClient.Hub.WebhooksServer.updated_branches(event)
# ["main", "dev"]

updated_tags(arg1)

@spec updated_tags(map()) :: [String.t()]

Returns the list of updated tags from a repo content event.

verify_and_parse(payload, opts \\ [])

@spec verify_and_parse(
  map(),
  keyword()
) :: {:ok, map()} | {:error, String.t()}

Verifies a webhook payload's authenticity and parses it.

Options

  • :secret — your webhook's configured secret (required for verification)
  • :header_secret — value of the X-Webhook-Secret header from the request
  • :skip_verification — skip secret verification (not recommended for production)

Returns

  • {:ok, parsed_event} — map with structured webhook data
  • {:error, reason} — string explaining what went wrong

Example

# In a Phoenix controller
case HuggingfaceClient.Hub.WebhooksServer.verify_and_parse(conn.body_params,
       secret: "my-webhook-secret",
       List.first(header_secret: get_req_header(conn, "x-webhook-secret"))
     ) do
  {:ok, event} -> handle_event(event)
  {:error, msg} -> Logger.warning("Invalid webhook: #{msg}")
end

verify_secret(expected_secret, opts)

@spec verify_secret(
  String.t(),
  keyword()
) :: :ok | {:error, String.t()}

Verifies that the X-Webhook-Secret header matches the configured secret.

Returns :ok or {:error, reason}.

Example

:ok = HuggingfaceClient.Hub.WebhooksServer.verify_secret(
  "my-webhook-secret",
  header_secret: "my-webhook-secret"
)