View Source WorkOS.Webhooks (WorkOS SDK for Elixir v1.1.0)

Manage timestamp and signature validation of Webhooks in WorkOS.

See https://workos.com/docs/webhooks

Summary

Functions

Verify webhook payload and return an event.

Functions

Link to this function

construct_event(payload, sigHeader, secret, tolerance \\ 180)

View Source
@spec construct_event(
  payload :: String.t(),
  sigHeader :: String.t(),
  secret :: String.t(),
  tolerance :: pos_integer()
) :: {:ok, payload :: map()} | {:error, message :: String.t()}

Verify webhook payload and return an event.

payload is the raw, unparsed content body sent by WorkOS, which can be retrieved with Plug.Conn.read_body/2, Note that Plug.Parsers will read and discard the body, so you must implement a custom body reader if the plug is located earlier in the pipeline.

sigHeader is the value of workos-signature header, which can be fetched with Plug.Conn.get_req_header/2, i.e. Plug.Conn.get_req_header(conn, "workos-signature").

secret is your webhook endpoint's secret from the WorkOS Dashboard.

tolerance is the allowed deviation in seconds from the current system time to the timestamp found in signature. Defaults to 180 seconds (3 minutes).

WorkOS API reference: https://workos.com/docs/webhooks/securing-your-webhook-endpoint/validating-events-are-from-workos

Example plug in your consuming application which places the constructed

event in the conn assigns

defmodule MyAppWeb.WorkOSWebhooksPlug do @behaviour Plug

alias Plug.Conn

def init(config), do: config

# Match on any requests from workos, i.e. the Endpoint URL configured in # the WorkOS Dashboard, adjust @requestpath as appropriate @request_path "/webhooks/workos" def call(%{request_path: @request_path} = conn, ) do

signing_secret = Application.get_env(:workos, :webhook_signing_secret)
[worksos_signature] = Conn.get_req_header(conn, "workos-signature")

with {:ok, body, _} <- Conn.read_body(conn),
     {:ok, workos_event} <-
       WorkOS.Webhooks.construct_event(body, worksos_signature, signing_secret) do
  Conn.assign(conn, :workos_event, workos_event)
else
  {:error, error} ->
    conn
    |> Conn.send_resp(:bad_request, error.message)
    |> Conn.halt()
end

end

def call(conn, _), do: conn end

As per the aforementioned note about Plug.Parsers the above plug would need to precede these in Endpoint.ex ... i.e.

defmodule MyAppWeb.Endpoint do ... ...

plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]

plug MyAppWeb.WorkOSWebhooksPlug

plug Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["/"], json_decoder: Phoenix.json_library() ... ...