Verifies HubSpot v3 webhook signatures and parses event batches.
Configure the app's client secret as the inbound declaration's secret.
The signature is a base64 HMAC-SHA256 over
requestMethod + requestUri + requestBody + timestamp, with no separators.
The signature and millisecond timestamp arrive in
X-HubSpot-Signature-v3 and X-HubSpot-Request-Timestamp.
Pass the raw body, HTTP method, and complete public request URI to ingress. A configured public base URL preserves the external scheme, host, and port when your app sits behind a proxy:
public_base_url = Application.fetch_env!(:my_app, :public_webhook_base_url)
query = if conn.query_string == "", do: "", else: "?" <> conn.query_string
request_uri = public_base_url <> conn.request_path <> queryConfigure the base URL without a trailing slash. The provider applies the
percent-decoding rules in decode_request_uri/1 before verifying. Preserve
the URI supplied to your HTTP endpoint; rebuilding it from decoded query
parameters can change the signed value. See HubSpot's
request validation guide.
The default replay window is 300 seconds in either direction: timestamps
too far in the past or future are rejected. replay_window_seconds on the
inbound declaration overrides this value. Keep application clocks synchronized.
The decoded payload must be a nonempty list of events. Known homogeneous
batches map to one event-type atom; mixed batches map to :mixed. An unknown
subscriptionType returns {:error, :unknown_event_type} and ingress records
a permanent failure. The finite type map avoids creating atoms from input.
event_identity/1 hashes the complete batch. It ignores each outer event's
retry-varying attemptNumber, canonicalizes object keys and outer event
order, and preserves nested list order and repeated events. The identity
therefore represents the batch rather than any one eventId.
handle_event/2 returns an AshHooks.Provider.HubSpotV3.Event; it does not
perform your application's business actions. To process events during ingress,
write a provider that delegates verification, parsing, and event_identity/1
to this module and implements an idempotent handle_event/2.
Summary
Functions
Decodes the vendor's documented percent-encodings in a signed request URI
(%3A %2F %3F %40 %21 %24 %27 %28 %29 %2A %2C %3B, either hex case) —
HubSpot signs the DECODED form, so the verifier must decode before
hashing. The query-separator ? itself is unaffected (only its encoded
%3F form decodes).
Builds a typed HubSpot v3 event from a verified batch payload.
Functions
Decodes the vendor's documented percent-encodings in a signed request URI
(%3A %2F %3F %40 %21 %24 %27 %28 %29 %2A %2C %3B, either hex case) —
HubSpot signs the DECODED form, so the verifier must decode before
hashing. The query-separator ? itself is unaffected (only its encoded
%3F form decodes).
Builds a typed HubSpot v3 event from a verified batch payload.