---
name: evolution-hubspot-integration
description: "HubSpot CRM integration for Bioaction — living doc, locked decisions, commercial envelope, and SDK gotchas"
metadata: 
  node_type: memory
  type: project
  originSessionId: fde44e5f-0515-46c5-a42b-29f2e879e184
  modified: 2026-07-28T12:06:20.868Z
---

HubSpot CRM integration for **Bioaction** (tenant `85-1701079776`). Living doc: `evolution/audits/HUBSPOT_INTEGRATION_PROJECT.md`. Branch `hubspotIntegration` (off `staging`). Opened 2026-07-28, OPEN.

**Tickets/commercials:** parent #1128, children 1130 (Phase 1), 1306 (Phase 2), 1307 (Phase 3); 1129+1131 closed "not relevant". Job 47 + quote 3196 in Evolution's OWN billing tenant `85-1684121907` (NOT the customer tenant — looking up job 47 in the customer db returns an unrelated job). **34 sold hours / $4,896 @ $144/hr (cost $75).** Subticket estimates totalled 94 h — ~3× the quote; the build is deliberately shaped to fit 34.

**Decisions locked:** Evolution is an OAuth2 *client* to HubSpot, never an authorization server (`league/oauth2-server` rejected, 1129 stays closed). Use `hubspot/api-client` ^14.1's built-in OAuth2, not a private-app token. Inbound is **cron pull** via CRM search on `hs_lastmodifieddate`, no webhooks in MVP.

**Gotchas:**
- SDK discovery method is `oauth()` lowercase — `oAuth()` throws `Unable to discover "oAuth" client`.
- HubSpot access tokens expire in **30 min**; refresh tokens never expire. Every cron run must refresh first.
- Search endpoints capped at **4 req/sec** — the throttle point for the pull; needs resumable watermark + backoff.
- Prod rate limits come from the *customer's* subscription tier, not our developer account.
- Dev test account hub id **443502491** (Enterprise, all hubs) — Enterprise is for prototyping paid variations only, must not leak into MVP scope.
- `plugins/vendor/` is gitignored → `composer install` must be run on prod after merge; git sync alone won't ship the SDK. See [[evolution-workspace-mapping]].
- Object mapping: Evolution `contacts` (organisation) → HubSpot **Company**; addressbook person → HubSpot **Contact**. Getting this backwards is a rewrite.
- Apps must be created via the **HubSpot CLI projects platform** (`hs`, not `hubspot`; legacy app creation removed March 2026). Two accounts, easy to conflate: `hs project upload` targets the **app developer account**; test account 443502491 is only where the app is *installed*. A `standard` portal can host neither.
- `hs init` drops `hubspot.config.yml` (plaintext personal access key) into the CWD — never run it inside the web root. Global config is `/config/.hscli/config.yml`; `hs config migrate` relocates it.
- **Never `cat`/`sed` a credential file to inspect it** — base64 values wrap onto the line after their label, so line-based redaction leaks them. Cost a live key rotation on 2026-07-28.
- `hs project create` generates every optional feature (cards/pages/settings/workflow-actions/webhooks). MVP ships only `src/app/app-hsmeta.json`; `hs project add` regenerates the rest when a variation sells.

**Also found:** `apps/api/library/auth.php:26` echoes `"No Credentialks Deny"` with `deny()` commented out — a no-credentials request falls through instead of 401ing. Pre-existing auth bypass, not this project's scope. `evolution/apps/` is a stale diverged copy of the separate `apps` repo — never build there. See [[workspace-repos]].

Variations tracked in the doc (V1–V8); V3 (sites sync) is already quoted at $864 and unsold. V8 = productise the plugin for other tenants, which is why Evolution must own the HubSpot developer account, not Bioaction.
