Humaans.Webhooks (Humaans v0.6.0)

Copy Markdown View Source

Functions for managing Humaans webhook subscriptions and verifying incoming webhook deliveries.

Managing webhook subscriptions

Standard CRUD on the /api/webhooks endpoint via the generated list/2, create/2, retrieve/2, update/3, and delete/2 functions.

Verifying a delivery

Humaans signs webhook payloads with HMAC-SHA256 using your endpoint's signing secret. Verifying the signature on every delivery is required — treat any unverified payload as untrusted input.

defp verify(conn, secret) do
  {:ok, raw_body, conn} = Plug.Conn.read_body(conn)
  signature = Plug.Conn.get_req_header(conn, "x-humaans-signature") |> List.first()

  case Humaans.Webhooks.verify_signature(raw_body, signature, secret) do
    :ok -> {:ok, conn, raw_body}
    {:error, _reason} = err -> err
  end
end

Always pass the raw request body to verify_signature/3. Re-serialised JSON will not match because byte-level differences (key ordering, whitespace) change the HMAC.

Summary

Functions

Creates a new webhook.

Deletes a specific webhook by ID.

Lists all webhooks.

Retrieves a specific webhook by ID.

Updates a specific webhook by ID.

Verifies a webhook signature against the raw request body.

Types

delete_response()

@type delete_response() ::
  {:ok, %{id: String.t(), deleted: bool()}} | {:error, Humaans.Error.t()}

list_response()

@type list_response() ::
  {:ok,
   [
     %Humaans.Resources.Webhook{
       company_id: term(),
       created_at: term(),
       events: term(),
       id: term(),
       updated_at: term(),
       url: term()
     }
   ]}
  | {:error, Humaans.Error.t()}

response()

@type response() ::
  {:ok,
   %Humaans.Resources.Webhook{
     company_id: term(),
     created_at: term(),
     events: term(),
     id: term(),
     updated_at: term(),
     url: term()
   }}
  | {:error, Humaans.Error.t()}

Functions

create(client, params)

@spec create(client :: map(), params :: map() | keyword()) ::
  {:ok, Humaans.Resources.Webhook.t()} | {:error, Humaans.Error.t()}

Creates a new webhook.

Parameters

  • client - Client created with Humaans.new/1
  • params - Map of attributes for the new webhook

Examples

client = Humaans.new(access_token: "your_access_token")
params = %{url: "https://example.com/hooks", events: ["person.created"]}
{:ok, webhook} = Humaans.Webhooks.create(client, params)

delete(client, id)

@spec delete(client :: map(), id :: String.t()) ::
  {:ok, %{id: String.t(), deleted: boolean()}} | {:error, Humaans.Error.t()}

Deletes a specific webhook by ID.

Parameters

  • client - Client created with Humaans.new/1
  • id - String ID of the webhook to delete

Examples

client = Humaans.new(access_token: "your_access_token")
{:ok, %{id: "webhook_id", deleted: true}} = Humaans.Webhooks.delete(client, "webhook_id")

list(client, params \\ %{})

@spec list(client :: map(), params :: map() | keyword() | [{String.t(), term()}]) ::
  {:ok, [Humaans.Resources.Webhook.t()]} | {:error, Humaans.Error.t()}

Lists all webhooks.

Returns a list of webhooks matching the optional filter params.

Parameters

  • client - Client created with Humaans.new/1
  • params - Optional map of filter parameters (default: %{})

Examples

client = Humaans.new(access_token: "your_access_token")
{:ok, webhooks} = Humaans.Webhooks.list(client)
{:ok, webhooks} = Humaans.Webhooks.list(client, %{})

retrieve(client, id)

@spec retrieve(client :: map(), id :: String.t()) ::
  {:ok, Humaans.Resources.Webhook.t()} | {:error, Humaans.Error.t()}

Retrieves a specific webhook by ID.

Parameters

  • client - Client created with Humaans.new/1
  • id - String ID of the webhook to retrieve

Examples

client = Humaans.new(access_token: "your_access_token")
{:ok, webhook} = Humaans.Webhooks.retrieve(client, "webhook_id")

update(client, id, params)

@spec update(client :: map(), id :: String.t(), params :: map() | keyword()) ::
  {:ok, Humaans.Resources.Webhook.t()} | {:error, Humaans.Error.t()}

Updates a specific webhook by ID.

Parameters

  • client - Client created with Humaans.new/1
  • id - String ID of the webhook to update
  • params - Map of attributes to update

Examples

client = Humaans.new(access_token: "your_access_token")
params = %{events: ["person.created", "person.updated"]}
{:ok, webhook} = Humaans.Webhooks.update(client, "webhook_id", params)

verify_signature(payload, signature, secret)

@spec verify_signature(
  payload :: binary(),
  signature :: String.t() | nil,
  secret :: String.t() | nil
) :: :ok | {:error, :invalid_signature | :missing_signature | :missing_secret}

Verifies a webhook signature against the raw request body.

Computes HMAC-SHA256(secret, payload) and compares it to signature in constant time.

Accepts the signature as either a raw hex string ("abc123...") or with a sha256= prefix ("sha256=abc123..."), so it works regardless of the exact convention used by the sender.

Returns

  • :ok - Signature matches.
  • {:error, :invalid_signature} - Signature does not match the payload.
  • {:error, :missing_signature} - signature is nil or empty.
  • {:error, :missing_secret} - secret is nil or empty.

Examples

iex> body = ~s({"event":"person.updated"})
iex> secret = "test-secret"
iex> sig =
...>   :crypto.mac(:hmac, :sha256, secret, body)
...>   |> Base.encode16(case: :lower)
iex> Humaans.Webhooks.verify_signature(body, sig, secret)
:ok

iex> Humaans.Webhooks.verify_signature("body", "deadbeef", "secret")
{:error, :invalid_signature}

iex> Humaans.Webhooks.verify_signature("body", nil, "secret")
{:error, :missing_signature}