AshHooks.Provider.ComplyCube (AshHooks v0.2.2)

Copy Markdown View Source

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).