# `KapsoEx.Webhook`
[🔗](https://github.com/rocket4ce/kapso_ex/blob/v0.1.0/lib/kapso_ex/webhook.ex#L1)

Verifies and decodes webhooks sent by Kapso.

Kapso signs every webhook (project webhooks and both `kapso` and `meta`
kind phone number webhooks) with an HMAC-SHA256 of the **raw request body**
using the webhook's `secret_key`, hex encoded, in the `X-Webhook-Signature`
header.

Other useful headers:

  * `X-Webhook-Event` - event name, for example `whatsapp.message.received`.
  * `X-Idempotency-Key` - unique per delivery; use it to drop duplicates,
    since webhooks can be delivered more than once.
  * `X-Webhook-Batch: true` - the body is a batch envelope
    (`%{"batch" => true, "data" => [...]}`); see `payloads/1`.

## Phoenix example

The signature must be computed over the exact bytes Kapso sent, so keep a
copy of the raw body with `KapsoEx.Webhook.CacheBodyReader`:

    # endpoint.ex
    plug Plug.Parsers,
      parsers: [:urlencoded, :multipart, :json],
      pass: ["*/*"],
      body_reader: {KapsoEx.Webhook.CacheBodyReader, :read_body, []},
      json_decoder: Jason

    # controller
    def create(conn, _params) do
      raw_body = KapsoEx.Webhook.CacheBodyReader.raw_body(conn)
      [signature] = get_req_header(conn, "x-webhook-signature")
      [event] = get_req_header(conn, "x-webhook-event")

      case KapsoEx.Webhook.construct_event(raw_body, signature, webhook_secret()) do
        {:ok, body} ->
          for payload <- KapsoEx.Webhook.payloads(body), do: MyApp.handle(event, payload)
          send_resp(conn, 200, "ok")

        {:error, _reason} ->
          send_resp(conn, 401, "invalid signature")
      end
    end

Respond within 10 seconds and move heavy work to a background job.

# `construct_event`

```elixir
@spec construct_event(binary(), String.t() | nil, String.t()) ::
  {:ok, map()} | {:error, :invalid_signature | Jason.DecodeError.t()}
```

Verifies the signature and decodes the JSON body.

Returns `{:error, :invalid_signature}` when the signature does not match
and `{:error, %Jason.DecodeError{}}` when the body is not valid JSON.

# `payloads`

```elixir
@spec payloads(map()) :: [map()]
```

Returns the list of event payloads in a decoded webhook body.

Buffered `whatsapp.message.received` webhooks arrive as a batch envelope
with the payloads under `"data"`; every other webhook carries a single
payload.

## Examples

    iex> KapsoEx.Webhook.payloads(%{"batch" => true, "data" => [%{"message" => 1}, %{"message" => 2}]})
    [%{"message" => 1}, %{"message" => 2}]

    iex> KapsoEx.Webhook.payloads(%{"message" => 1})
    [%{"message" => 1}]

# `verify_meta_signature`

```elixir
@spec verify_meta_signature(binary(), String.t() | nil, String.t()) :: boolean()
```

Verifies a webhook sent **directly by Meta** (not forwarded by Kapso),
using the `X-Hub-Signature-256` header (`"sha256=<hex>"`) and your Meta
app secret.

# `verify_signature`

```elixir
@spec verify_signature(binary(), String.t() | nil, String.t()) :: boolean()
```

Returns `true` when `signature` (the `X-Webhook-Signature` header) matches
the HMAC-SHA256 of `payload` with `secret`.

The comparison runs in constant time. A `nil` or malformed signature
returns `false`.

---

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