Zep.Webhook (Zep v1.0.0)

Copy Markdown View Source

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
end

It 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

verify(raw_body, headers, secret)

@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.