Skip to main content

HubSpot

One connected HubSpot account per harnesi user lets the assistant read and, after your approval, write CRM records. See Vendor APIs for how the tools reach HubSpot.

What the user is trying to do​

Typical turns: "who am I on HubSpot?", "list contacts whose lead status is Connected", "create this deal after I approve the fields". Writes require an explicit approval in a prior turn (the tool descriptions say so). Recipients / HubSpot audit logs see the connected user's portal, not a shared service account.

1. One-time: save the HubSpot OAuth app​

A superuser pastes a HubSpot app client ID and secret into Tools → HubSpot (chat/vault_api.py). Those values live encrypted in VaultSecret, not in .env. The redirect URI that HubSpot must allow is:

http://localhost:8000/api/integrations/hubspot/callback

(local ./dev.sh; derived from BACKEND_BASE_URL). Consent scopes start with oauth plus CRM read/write for contacts, leads, deals, and companies (chat/hubspot_oauth_api.py). Widen via the vault; already-connected users reconnect to pick up new scopes.

Until both client ID and secret are stored, Connect HubSpot account stays disabled — GET /api/integrations/hubspot/status reports configured: false.

2. Connect this user's HubSpot account​

The user clicks Connect. The browser goes to /api/integrations/hubspot/authorize, then to HubSpot's consent screen. HubSpot redirects to the backend callback. CSRF/state is a signed payload (user id + return path + SPA origin), not the Django session cookie — HubSpot's redirect hits port 8000, which often has no SPA cookie.

On success, HubspotCredential is upserted: refresh token, a cached access token, and display fields (hub_id, hub_domain, hubspot_user_email) from HubSpot's token-info endpoint at connect time only (Django has the real token then). That row is the entitlement bit.

Disconnect deletes the row. Connecting after a warm session is already running does not attach the proxy — start a new chat so session start runs again.

Verification​

  1. Restart backend; start a new conversation (proxy + env are set at container start).
  2. Tools page: enter client id/secret, click Connect HubSpot, complete consent, confirm the panel shows connected + hub domain.
  3. In chat, run hubspot_whoami → reports portal id / UI domain.
  4. Token snapshot: a call after ~30 minutes 401s until the session is recreated.
  5. Unconnected user → "connect a HubSpot account … and start a new chat session".
  6. session.json contains env://HUBSPOT_ACCESS_TOKEN and never the real token; allow_vars has HUBSPOT_BASE_URL / HUBSPOT_API_KEY, not HUBSPOT_ACCESS_TOKEN.