AshHooks.Legacy (AshHooks v2.0.4)

Copy Markdown View Source

The legacy HMAC webhook envelope for receivers using the :legacy or :dual signing modes.

AshHooks.Legacy.verify/5 validates its signature.

Envelope:

  • MAC — HMAC-SHA256 over "<unix_ts>.<body>", lowercase hex.
  • sign/3 → "v1=<hex>"; rotation appends ",v1prev=<hex>" under the separately imported previous secret.
  • header — x-webhook-signature: t=<unix_ts>,<signature>.

Use :dual to send this envelope alongside Standard Webhooks while receivers migrate. New integrations should use :standard.

Summary

Functions

Assembles the header value: t=<unix_ts>,<signature>.

The legacy header set: x-webhook-signature under secret (and optional previous).

HMAC-SHA256 over <unix_ts>.<body>, lowercase hex, v1=-prefixed.

Current and previous signatures comma-joined (zero-downtime rotation).

Verifies a legacy header value: parses t=/v1=, checks the replay window, compares hex digests in constant time.

Functions

header_value(signature, unix_ts)

@spec header_value(String.t(), integer()) :: String.t()

Assembles the header value: t=<unix_ts>,<signature>.

headers(secret, previous \\ nil, body, unix_ts)

@spec headers(binary(), binary() | nil, binary(), integer()) :: %{
  required(String.t()) => String.t()
}

The legacy header set: x-webhook-signature under secret (and optional previous).

sign(secret, body, unix_ts)

@spec sign(binary(), binary(), integer()) :: String.t()

HMAC-SHA256 over <unix_ts>.<body>, lowercase hex, v1=-prefixed.

sign_with_previous(current_secret, previous_secret, body, unix_ts)

@spec sign_with_previous(binary(), binary(), binary(), integer()) :: String.t()

Current and previous signatures comma-joined (zero-downtime rotation).

verify(secret, body, header, now_ts, window \\ 300)

@spec verify(binary(), binary(), binary(), integer(), integer()) ::
  {:ok, %{unix_ts: integer()}} | {:error, :invalid_signature | :stale_timestamp}

Verifies a legacy header value: parses t=/v1=, checks the replay window, compares hex digests in constant time.

window defaults to 300 seconds, symmetric around now_ts.