# `HuggingfaceClient.Hub.WebhooksServer`
[🔗](https://github.com/huggingface/huggingface_client/blob/v0.1.0/lib/huggingface_client/hub/collaboration/webhooks_server.ex#L1)

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

# `comment_event?`

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

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

# `discussion_event?`

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

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

# `parse_event`

```elixir
@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?`

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

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

# `repo_config_event?`

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

Returns `true` if the event is a repository configuration change.

# `repo_content_event?`

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

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

# `updated_branches`

```elixir
@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`

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

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

# `verify_and_parse`

```elixir
@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`

```elixir
@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"
    )

---

*Consult [api-reference.md](api-reference.md) for complete listing*
