Verifies Ramp webhook signatures and decodes webhook payloads into
Ramp.Webhooks.Event structs.
Ramp signs each webhook delivery with HMAC-SHA256 over the raw request
body, sent in the X-Webhook-Signature header, alongside a timestamp in
X-Webhook-Timestamp that lets you additionally guard against replay of
captured deliveries.
Always verify against the raw, unparsed request body -- reserializing
JSON can change byte-for-byte formatting and break the signature check.
In a Phoenix app, read the raw body before your JSON parser plug runs
(e.g. with a custom Plug.Parsers body reader), or read it directly with
Plug.Conn.read_body/2 in a plug placed ahead of Plug.Parsers.
Example (Plug/Phoenix controller)
def webhook(conn, _params) do
{:ok, raw_body, conn} = Plug.Conn.read_body(conn)
signature = conn |> Plug.Conn.get_req_header("x-webhook-signature") |> List.first()
timestamp = conn |> Plug.Conn.get_req_header("x-webhook-timestamp") |> List.first()
case Ramp.Webhooks.construct_event(raw_body, signature, timestamp, @webhook_secret) do
{:ok, event} ->
MyApp.WebhookProcessor.handle(event)
send_resp(conn, 200, "ok")
{:error, %Ramp.Error{}} ->
send_resp(conn, 400, "invalid signature")
end
end
Summary
Functions
Verifies the signature (and optional timestamp), then decodes raw_body
into a Ramp.Webhooks.Event on success.
Verifies an HMAC-SHA256 webhook signature using a constant-time comparison
(:crypto.hash_equals/2).
Verifies both the signature and, if timestamp is given, that the
delivery falls within tolerance_seconds (default
300) of now -- protecting against replay of a
captured payload + signature.
Functions
@spec construct_event(binary(), String.t(), String.t() | nil, String.t(), keyword()) :: {:ok, Ramp.Webhooks.Event.t()} | {:error, Ramp.Error.t()}
Verifies the signature (and optional timestamp), then decodes raw_body
into a Ramp.Webhooks.Event on success.
Options
:tolerance_seconds- overrides the default replay-protection window
@spec verify(binary(), String.t(), String.t()) :: :ok | {:error, Ramp.Error.t()}
Verifies an HMAC-SHA256 webhook signature using a constant-time comparison
(:crypto.hash_equals/2).
signature is the raw value of the X-Webhook-Signature header (either
the bare hex digest, or one prefixed with sha256= -- both are accepted).
@spec verify(binary(), String.t(), String.t() | nil, String.t(), non_neg_integer()) :: :ok | {:error, Ramp.Error.t()}
Verifies both the signature and, if timestamp is given, that the
delivery falls within tolerance_seconds (default
300) of now -- protecting against replay of a
captured payload + signature.
timestamp is the raw value of the X-Webhook-Timestamp header (Unix
seconds, as a string). Pass nil to skip the freshness check.