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 examplewhatsapp.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" => [...]}); seepayloads/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
endRespond within 10 seconds and move heavy work to a background job.
Summary
Functions
Verifies the signature and decodes the JSON body.
Returns the list of event payloads in a decoded webhook body.
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.
Returns true when signature (the X-Webhook-Signature header) matches
the HMAC-SHA256 of payload with secret.
Functions
@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.
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}]
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.
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.