ComplyCube webhook verifier: lowercase-hex HMAC-SHA256 of the RAW request
body under the webhook endpoint's secret, carried in the
ComplyCube-Signature header — no timestamp, no replay window (verified
against the vendor docs and all three official SDKs; the acceptance vector
is the PHP SDK's own test fixture, quoted in
test/ash_hooks/provider/comply_cube_test.exs).
The scheme is exactly "lowercase-hex HMAC over the raw body", so
verify_signature/3 delegates to Provider.default_verify_signature/4:
constant-time compare behind a byte-size guard, case-sensitive on the hex
(the strict Python/PHP SDKs compare case-sensitively — Node's uppercase
acceptance is hex-decode leniency, not evidence of uppercase emission), and
an empty secret fails closed as {:error, :no_webhook_secret}. Sign the raw
body bytes exactly as received — re-serialized JSON with different
whitespace fails verification.
parse_event_type/1 maps the vendor's documented event types to pre-existing
atoms via an allowlist — unknown type strings fail closed as
{:error, :unknown_event_type} (no atom-table growth from webhook input).
The consequence is deliberate: a NEW vendor event type the map does not know
lands in the ledger as failed_permanent with error_class
"unknown_event_type" — recorded and auditable, but not re-processed
automatically. Extend @event_types (one module attribute) when ComplyCube
publishes new types, or write a permissive provider module against
AshHooks.Provider if your consumer wants different semantics.
The optional secret callbacks are deliberately NOT implemented: ComplyCube
issues one secret per configured webhook endpoint, and an inbound
declaration IS one endpoint — supply it through the DSL secret source.
timestamp_header/0 is likewise not implemented: a replay_window_seconds
on a ComplyCube inbound is rejected at compile time (the scheme has no
trustworthy timestamp to window over).