CRM
HubSpot → BigQuery → dbt.
Five minutes from signup.
Every deal stage change, every contact creation, every property mutation — typed and queryable. Hooky generates a typed stg_hubspot.sql from the events you've already received, and pings you when HubSpot changes the shape.
Shape
Object change events, deeply nested association edges, occasional schema mutations.
Common events
contact.creation · deal.propertyChange · company.deletion · ticket.propertyChange
[{
"eventId": 1715812844001,
"subscriptionType": "deal.propertyChange",
"portalId": 8821192,
"objectId": 7849421,
"propertyName": "dealstage",
"propertyValue": "closedwon",
"occurredAt": 1715812844000
}]
The output
Here's the dbt model Hooky generates for HubSpot.
select
cast(json_value(payload, '$.eventId') as int64) as event_id_hs,
json_value(payload, '$.subscriptionType') as subscription_type,
cast(json_value(payload, '$.portalId') as int64) as portal_id,
cast(json_value(payload, '$.objectId') as int64) as object_id,
json_value(payload, '$.propertyName') as property_name,
json_value(payload, '$.propertyValue') as property_value,
cast(json_value(payload, '$.occurredAt') as int64) as occurred_at_unix,
payload as raw_payload
from {{ source('hooktopus', 'events') }}
where endpoint_name = 'hubspot'
Yes, you can hand-edit it. Yes, regenerate is non-destructive: it produces a diff, doesn't auto-merge. Yes, every column has raw_payload as the escape hatch.
The hard part
HubSpot batches events as arrays.
We unnest at ingest so each batched event becomes its own row with a stable event_id. You write one dbt model, not a CTE of unnesting gymnastics.
What you get
Everything you need for HubSpot in production.
Signature verification, handled
We use Hookdeck for ingress signature checks. HubSpot's scheme works out of the box — paste your signing secret, done.
Drift alerts via Slack + email
New field? Type change? Field disappearing? You hear about it within the hour, with a sample event and a regenerate link.
Replay from R2 archive
Bad BQ window? Re-write any time range from R2 with one click. HubSpot retries don't disappear.
Native JSON type, no string-of-JSON
Apostrophes, emojis, special chars in HubSpot payloads survive intact. Query with JSON_VALUE, get clean strings.
Partitioned + clustered table
PARTITION BY DATE(received_at) CLUSTER BY endpoint_name so even a chatty HubSpot firehose stays cheap to query.
Never blocking writes
Events archive to R2 before the destination write. HubSpot sees a 202, you never lose an event during a BQ blip.
Catch HubSpot today
Your hubspot table, ready before lunch.
Free under 10k events/month. Add the URL to HubSpot, run dbt, ship a chart.