Verifies the authenticity of incoming Zep webhook deliveries.
Zep signs every webhook using Svix, which uses
HMAC-SHA256 over "{svix-id}.{svix-timestamp}.{body}", keyed by your
endpoint's signing secret (base64-decoded, with the whsec_ prefix
stripped). Webhook endpoint management (creating/listing/testing
endpoints, rotating the signing secret) is done from the Zep dashboard,
not the API - this module only covers verifying deliveries your own
server receives.
Event types include episode.processed, ingest.batch.completed,
byom.rate_limited, and byom.request_failed - see the Webhooks guide
for full payload shapes per event.
Usage (e.g. in a Plug/Phoenix controller)
def webhook(conn, _params) do
{:ok, raw_body, conn} = Plug.Conn.read_body(conn)
headers = %{
"svix-id" => List.first(Plug.Conn.get_req_header(conn, "svix-id")),
"svix-timestamp" => List.first(Plug.Conn.get_req_header(conn, "svix-timestamp")),
"svix-signature" => List.first(Plug.Conn.get_req_header(conn, "svix-signature"))
}
case Zep.Webhook.verify(raw_body, headers, signing_secret()) do
:ok -> handle_event(Jason.decode!(raw_body))
{:error, reason} -> send_resp(conn, 400, "invalid signature: #{reason}")
end
endIt is essential to verify against the raw request body exactly as received - many web frameworks parse JSON before your handler runs, which will break verification. Read the raw body first.
Summary
Functions
Verifies a webhook delivery's svix-signature header against the
computed HMAC.
Functions
@spec verify(String.t(), %{optional(String.t()) => String.t() | nil}, String.t()) :: :ok | {:error, :missing_headers | :invalid_secret | :signature_mismatch}
Verifies a webhook delivery's svix-signature header against the
computed HMAC.
headers is a map (string keys) containing at least "svix-id",
"svix-timestamp", and "svix-signature". secret is the endpoint's
signing secret as shown in the dashboard, including its whsec_ prefix.
svix-signature may contain multiple space-separated v1,<base64>
values (for secret rotation) - this matches against any of them.
Returns :ok, or {:error, reason} with reason one of
:missing_headers, :invalid_secret, or :signature_mismatch.