AshHooks.Provider.HubSpotV3 (AshHooks v2.0.4)

Copy Markdown View Source

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 <> query

Configure 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

decode_request_uri(uri)

@spec decode_request_uri(String.t()) :: String.t()

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

handle_event(event_type, payload)

Builds a typed HubSpot v3 event from a verified batch payload.