Skip to content

feat(connect): HubSpot OAuth instead of manual Private App token #3610

@louis030195

Description

@louis030195

Problem

Current crates/screenpipe-connect/src/connections/hubspot.rs uses a manual Private App token field (api_token, secret, pasted by the user). Friction:

  • User has to navigate to HubSpot Settings → Integrations → Private Apps → create app → enable scopes (contacts/companies/deals read+write) → copy token → paste into screenpipe. ~90 seconds even for experienced HubSpot admins.
  • Token rotation = user has to redo the whole flow.
  • We lose half the prospects who'd otherwise connect — the friction is the killer.
  • Internal agents (Claude Code sessions, scheduled pipes, etc.) hit the same wall — can't write to HubSpot without a human pasting a token first.

Surfaced today while trying to auto-update HubSpot from a Claude Code session — wanted to create a contact + company + update lifecycle stages, hit the no-token wall, ended up giving the user a paste-ready table instead of just doing it. Same friction every customer hits.

What's already there

  • crates/screenpipe-connect/src/connections/hubspot.rs — Bearer-token impl, works, just manual
  • crate::oauth module — already used by other integrations (notion, slack, calcom, gmail, etc.)
  • crates/screenpipe-connect/src/connections/notion.rs is the cleanest reference impl (~30 LoC for the OAuth wiring)

Proposed change

Mirror the notion.rs pattern. Concretely:

  1. Register a HubSpot OAuth app in our screenpipe HubSpot dev account → get client_id (and store client_secret in the proxy server's env).
  2. Add OAuthConfig to hubspot.rs:
    static OAUTH: OAuthConfig = OAuthConfig {
        auth_url: "https://app.hubspot.com/oauth/authorize",
        client_id: "<our hubspot oauth app client id>",
        extra_auth_params: &[],
        redirect_uri_override: None,
    };
  3. Implement oauth_config() returning Some(&OAUTH), drop the manual api_token field from IntegrationDef.
  4. Update proxy_config() so ProxyAuth::Bearer { credential_key } reads the OAuth access token (same shape as notion).
  5. Update test() to call oauth::read_oauth_token_instance(secret_store, "hubspot", None) (mirror of notion).
  6. Token refresh: HubSpot OAuth tokens expire after 30 min — needs to plug into the existing refresh scheduler (OAuthRefreshScheduler) the same way Google Calendar / Notion do. HubSpot returns refresh_token with the standard OAuth flow.
  7. Required HubSpot scopes (start with these — minimum useful for sales-ops automation):
    • crm.objects.contacts.read + crm.objects.contacts.write
    • crm.objects.companies.read + crm.objects.companies.write
    • crm.objects.deals.read + crm.objects.deals.write
    • crm.schemas.contacts.read + crm.schemas.companies.read + crm.schemas.deals.read
    • oauth (always required)
  8. Keep api_token field as a fallback for power users who'd rather paste a Private App token (some enterprises ban OAuth apps for compliance). Make the OAuth flow the primary CTA in the UI.

Acceptance criteria

  • HubSpot tile in /connections shows a "Connect with HubSpot" button (OAuth flow), not a token paste field as primary
  • OAuth roundtrip works end-to-end: click → HubSpot consent page → redirect back → token stored → test() succeeds
  • Token refresh wired into OAuthRefreshScheduler so users don't get logged out after 30 min
  • MCP / agent tools (e.g. proxy POST /connections/hubspot/proxy/crm/v3/objects/contacts) can create/update contacts and companies using the OAuth-acquired token
  • Fallback Private App token paste still works for users who explicitly want it
  • Smoke test: from a fresh Clerk account, connect HubSpot via OAuth, create a test contact, see it appear in HubSpot UI — under 30 seconds end-to-end

Why this matters now

Sales-ops automation (auto-log calls, auto-create contacts from new Pro signups, auto-update lifecycle stages when a customer expands, etc.) is the killer use case for screenpipe → HubSpot. The Private App token friction is the only thing in the way. Every B2B-shape customer we have (Wildpack, OFF, Pattern, NMedtech, MatcHR, etc.) would benefit from the same automations.

Reference impl

crates/screenpipe-connect/src/connections/notion.rs — copy-paste-adapt the OAuth wiring, swap URLs + scopes.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions