Hooktopus

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

hubspot.payload.json
[{
  "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.

stg_hubspot.sql
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.