# `Ankusa.Verifier.Hmac`
[🔗](https://github.com/jamescarr/ankusa/blob/ankusa-v0.7.0/packages/ankusa/lib/ankusa/verifier/hmac.ex#L1)

Configurable HMAC signature verifier, driven by a
`%Ankusa.Verifier.Hmac.Scheme{}` descriptor instead of one module per provider.

`opts` must carry `:scheme` — either a named preset atom
(`:stripe | :github | :standard_webhooks | :shopify | :slack`, resolved through
`Ankusa.Verifier.Schemes`) or an inline `%Scheme{}` describing any body-HMAC
provider. `:secret` and `:tolerance` carry the key and replay window, exactly
as with the old bespoke verifiers.

`:secret` is a binary or a list of binaries (current first); every key is
tried, so a rotation window is `secret: [new, old]` — hooks signed with
either verify while the provider switches over, and none land in quarantine.
An empty key would verify whatever anyone signs with it, so a missing or
empty secret, an empty list, or an element that is empty or does not decode
fails every hook with `:bad_secret`.

`:tolerance` is the replay window in seconds, 0 to
`Ankusa.Verifier.max_tolerance_seconds/0` (3600; default 300). A window wider
than an hour is not a window, so anything else fails every hook with
`:bad_tolerance` and refuses the boot of a source declared in config.

`hash: :sha1` on an inline scheme is allowed, for providers that still sign
with it, but it is no longer collision resistant: `weak_hash?/1` reports it,
boot logs a warning, and every such verification counts toward
`ankusa_verifier_weak_hash_total`.

Providers that are not body-HMAC — Twilio (SHA1 over the full request URL plus
sorted form params) and PayPal (RSA over a certificate fetched from a provider
URL) — are not expressible here and stay bespoke `Ankusa.Verifier` modules.

# `raw_key`

```elixir
@spec raw_key(String.t(), :raw | :whsec_base64) :: {:ok, binary()} | :error
```

The HMAC key a configured secret stands for: the string itself (`:raw`), or
the base64 after an optional `whsec_` prefix (`:whsec_base64`, Standard
Webhooks). `:error` when the base64 does not decode.

# `scheme_name`

```elixir
@spec scheme_name(keyword()) :: String.t()
```

Human name of the scheme for telemetry: the preset name, or `"custom"`.

# `validate_opts`

```elixir
@spec validate_opts(keyword()) ::
  :ok | {:error, :bad_secret | :bad_scheme | :bad_tolerance}
```

Check `opts` the way `verify/2` would use them, without a request: the
scheme resolves, `:tolerance` (when set) is an integer from 0 to
`Ankusa.Verifier.max_tolerance_seconds/0`, and every secret decodes to a
non-empty key. `:ok`, or the error `verify/2` would return for every hook.
Used at boot for sources declared in config
(`Ankusa.Verifier.validate_config!/1`).

# `weak_hash?`

```elixir
@spec weak_hash?(keyword()) :: boolean()
```

Whether `opts` verify with SHA-1, a hash that is no longer collision
resistant. No named preset uses it (`verifier_test.exs` asserts that), so
only an inline `%Scheme{hash: :sha1}` is weak. It works, because some
providers still sign that way; `Ankusa.Verifier.validate_config!/1` logs a
warning at boot and every verification increments
`ankusa_verifier_weak_hash_total`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
