Skip to content

feat: Hubspot integration#93

Open
nadyyym wants to merge 11 commits into
mainfrom
staging
Open

feat: Hubspot integration#93
nadyyym wants to merge 11 commits into
mainfrom
staging

Conversation

@nadyyym

@nadyyym nadyyym commented Jun 9, 2026

Copy link
Copy Markdown
Member

PR for release of Hubspot integration with Beton

Provides Inspector an ability to interact with Hubspot + unifies all integration interfaces in SourceAdapter and DestinationAdapter

Changes customer onboarding step – they can choose between different CRMs and next steps will adjust based on API methods in this integration and use them later on to create field mappings and send signal notifications

Tested end-to-end

nadyyym and others added 11 commits May 21, 2026 15:10
Introduces the source-read counterpart to the existing field-mapping
DestinationAdapter: a SourceAdapter interface + registry and canonical-object
normalization, with unit tests. Pulls CRM/event records IN to the warehouse;
mirror of @/lib/field-mapping (write side).

Interface validated 1:1 against the HubSpot client surface (getContacts/
getCompanies/getDeals + getObjectSchemas/getProperties + incremental polling).
Sync execution (durable runs) and crm_identity_map are deferred to F1.

Implements docs/roadmap-2026/E5-00-connector-framework.md (v2), decisions 1A/2A.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ports the self-contained HubSpot lib module from feature/BETON-hubspot-foundation
(124 commits behind staging) and rewires it to the standard integration_configs
connector model — one connection per workspace, like Attio/PostHog/Apollo.

- config.ts: integration_configs-backed credential retrieval (private-app token →
  api_key_encrypted; OAuth access token → api_key_encrypted, refresh/expiry/hub
  metadata/sync_cursor → config_json). Drops the bespoke hubspot_connections table.
- client.ts: createHubSpotClientForConnection → createHubSpotClientForWorkspace;
  OAuth refresh write-back now updates integration_configs.config_json.
- Drops polling.ts + the dedicated hubspot_connections/sync_state/records/
  associations tables — durable incremental sync + record landing move to F1.
- Migration: registry row only (integration_definitions); supported.ts += hubspot.
- Keeps client/auth/entities/associations/rate-limiter + 75 unit tests (all green).

Per docs/roadmap-2026/E5-01-hubspot.md; PR2 decisions 3A/4A + storage = integration_configs.
Next: hubspot DestinationAdapter + SourceAdapter wiring, validate/OAuth routes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires HubSpot into the connector frameworks (tests written first):

- field-mapping/hubspot-adapter.ts: DestinationAdapter (listObjects/listFields/
  fetchSampleSubjects/sendTest) over the HubSpot client + entities; registered
  in the unified /api/integrations/[name]/field-mappings route (VALID_DESTINATIONS
  += hubspot). Client/ops/sample-subjects injectable for unit tests.
- integrations/hubspot/source-adapter.ts: SourceAdapter (PR #90) — testConnection/
  listObjects/listFields/listRecords, incremental via hs_lastmodifieddate search,
  full pull via list endpoints, canonical-object normalization. Injectable client.
- /api/integrations/hubspot/validate: Private App token connect — validates then
  stores in integration_configs (OAuth authorize/callback deferred to follow-up).

20 new adapter tests (10 + 10), all green; tsc + build clean.

Per docs/roadmap-2026/E5-01-hubspot.md; decisions 4A. SourceAdapter is registered
when the source-read/F1 path lands (no runtime consumer yet — avoided dead code).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Register HubSpot source adapter at module load (getSourceAdapter('hubspot') now resolves)
- Extract shared adapterFor(name,ctx) destination dispatch; route preview-impact,
  sample-subjects, send-test + settings field-mapping page through it (was Attio-hardcoded)
- Add 'hubspot' signal target type: VALID_TARGET_TYPES + sync-signals cron write branch
- Tests for source registry, adapterFor, sync-signals hubspot branch

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New Choose-CRM picker step (segmented tabs); buildStepSequence collapses
  crm-category definitions into unified crm/crm_connect/crm_mapping block
- HubSpotStep: Private App token connect via /validate (pass/fail; static scopes ref)
- CrmMappingStep: CRM-routed, embeds existing FieldMappingPage (real endpoints)
- SetupWizard: selectedCrm routing + skip short-circuit; ~980px centered
- wizard-sequence tests updated (15/15)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ination

- Settings: /settings/integrations/hubspot — connection status, token reveal/edit
  (re-validates via /validate), disconnect, Overview + Field-mapping tabs
- signals/new: destination picker (PostHog/Attio/HubSpot) + HubSpot object-type
  sub-config; builds {type:'hubspot', external_id:<object>} target payload
- Read-only integrations.hubspot in setup status (does not gate setupComplete)
- Fix latent signal_id->signal_definition_id mismatch so cohort/list/hubspot
  targets actually attach on signal create
- destination-target payload unit tests (8/8)

Omitted (no backend): Attio upsert-record target, PostHog multi-cohort, sync
history/object-config/schedule, event-sequence composer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(integrations): SourceAdapter framework (read connectors)
… sync

Three review fixes on the HubSpot connector PR:

- signals/new: read the created id from `data.signal_definition.id` (POST returns
  { signal_definition }, not { signal }). Previously firstSignalId was always
  null, so the target-attach PATCH never fired and NO sync target was created
  for any destination (cohort/list/hubspot).
- cron/sync-signals: dispatch the HubSpot write by object type instead of always
  upsertContact — contacts upsert by email, companies upsert by the email's
  domain. Deals are rejected (createDeal has no dedup key, so a recurring cron
  would create duplicate deals every run). Non-email distinct_ids are skipped
  rather than erroring the whole target. Drop the unsupported "Deal" option from
  the destination picker.
- migration: replace the placeholder Brandfetch icon id with HubSpot's real
  brand id (idRt0LuzRf).

Adds sync-signals tests for the companies path, non-email skip, and the
unsupported-deals rejection. tsc clean; 157 targeted tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ker copy

- migration: ALTER TYPE sync_target_type ADD VALUE 'hubspot'. PR #91 introduced
  HubSpot as a signal destination but never added the enum value backing
  signal_sync_targets.target_type, so attaching a HubSpot target failed at the DB
  with: invalid input value for enum sync_target_type: "hubspot". Caught by live
  verification (unit tests mock Supabase). Verified live: PATCH attach now returns
  201 with target_type='hubspot'.
- signals/new: drop the now-removed "deal" from the HubSpot destination tile copy
  ("Upsert a contact or company.").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
merging hubspot integration built on top of new `SourceAdapter`. it is a first step of the effort to move all @getbeton's integrations to unified rails

end goal is to speed up integrations process and have a data interface (e.g. PostgreSQL tables with data on integrations + dedicated log drains for each integration + env vars etc) and single way of debugging the external integrations
@nadyyym nadyyym added this to the integrations: Hubspot milestone Jun 9, 2026
@nadyyym nadyyym requested a review from Sashmark97 June 9, 2026 04:12
@nadyyym nadyyym self-assigned this Jun 9, 2026
@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beton-inspector Ready Ready Preview, Comment Jun 9, 2026 4:12am

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant