HubSpot Integration

Two-way sync between HubSpot and Superkabe — import contacts, push activity to the timeline, mirror the suppression list.

What this integration does

  • Contact import. Pick any HubSpot list — Superkabe pulls every contact, applies your field mapping, and creates Superkabe leads.
  • Activity push. Every email.sent, email.opened, email.clicked, email.replied, and email.bounced event from Superkabe lands as a Note on the matching HubSpot contact's timeline.
  • Suppression sync. HubSpot contacts with hs_email_optout=true are pulled into Superkabe's block list — no risk of mailing someone who already opted out at the CRM level.
  • Idempotent. Activity pushes are deduped on (connection, lead, event_type, timestamp) so retries don't double-write.

One-time setup (admin)

You only need to do this once per Superkabe deployment. Superkabe's HubSpot Public App is created in your HubSpot Developer Account — every customer connects to that one app via OAuth.

1. Create the HubSpot Public App

  1. Sign in at developers.hubspot.com with the developer account that should own the listing.
  2. Apps → Create app → Public app.
  3. Name it "Superkabe". Add a logo + description.
  4. Auth tab → set Redirect URL to:
    https://api.superkabe.com/api/integrations/hubspot/callback
  5. Add OAuth scopes:
    • oauth
    • crm.objects.contacts.read
    • crm.objects.contacts.write
    • crm.lists.read
    • crm.schemas.contacts.read
    • timeline
  6. Save → grab the Client ID and Client secret from the Auth tab.

2. Set the env vars

Add these to Railway (production) or your .env (local):

HUBSPOT_CLIENT_ID=…
HUBSPOT_CLIENT_SECRET=…
HUBSPOT_REDIRECT_URI=https://api.superkabe.com/api/integrations/hubspot/callback

Restart the backend. On boot you should see [CRM_REGISTRY] registered provider hubspot in the logs — that confirms the env vars were picked up.

User flow (per customer)

What you'll see during consent: HubSpot displays a security warning on the consent screen for any third-party app that isn't Marketplace-verified, asking you to confirm you initiated the connection yourself. This is HubSpot's standard anti-phishing protection — it shows for every legitimate B2B integration on first connect to a new portal. Since you arrived at the screen by clicking Connect HubSpot on your own Superkabe dashboard, it's safe to proceed. The warning only appears on the very first authorization; future token refreshes happen server-side without a consent screen.

  1. Connect. Dashboard → Integrations → CRM → Connect HubSpot. The user is bounced to HubSpot, picks a portal, approves the scopes, and lands back on Superkabe with a green "Connected" badge.
  2. Import. Click Manage Import on the connected card. Pick a HubSpot list, map fields (defaults are auto-suggested for email/first_name/last_name/company/title/phone), click Start Import. The contact-import worker pulls 100 contacts per tick.
  3. Activity flows automatically. Once a contact is linked, every send/open/click/reply/bounce on that contact's lead gets pushed to the HubSpot contact's timeline as a Note. No additional configuration.
  4. Disconnect. Click Disconnect on the card. The OAuth tokens are wiped, pending pushes are cancelled, the contact links remain (so a future re-connect doesn't require re-importing).

What gets pushed to the timeline

Each event becomes a HubSpot Note attached to the contact. The Note body includes:

📤 Superkabe sent an email
Subject: Quick question about your hiring stack
[truncated body preview]
campaign_id: q2-outreach
mailbox: founder@yourdomain.com
via Superkabe · 2026-04-30T14:33:02.000Z

Each event-type uses a distinct emoji + headline so they're scannable on the timeline.

Rate limits and retries

  • HubSpot returns 429 when daily/burst limits are hit. The Superkabe client honors Retry-After automatically (one in-process retry up to 30s).
  • If a push fails with a retryable status (429, 5xx), the activity-push worker retries with exponential backoff: 1m, 5m, 15m, 1h, 4h, 12h.
  • After 6 failed attempts, the row is marked failed and surfaces in the dashboard's Failed counter.

Compliance & data handling

Data we read from HubSpot

Only the contact-shaped fields needed to send and report:

  • Email, first name, last name, full name
  • Company name, job title, phone number
  • Email-opt-out flag (hs_email_optout)

We never read deals, opportunities, notes, conversations, custom objects, or files. The crm.objects.contacts.read + crm.lists.read + timeline scopes are the minimum surface needed for the integration to function.

Data we write to HubSpot

One Note per Superkabe activity event, attached to the matching contact via the standard note → contact association. We do not modify contact properties, create/delete contacts, or touch any other object.

Webhook signature verification

Every inbound HubSpot webhook is verified against an HMAC-SHA256 signature using your app's client secret as the key (per HubSpot's v3 signature spec). Requests with missing, invalid, or replayed signatures (older than 5 minutes) are rejected with 401. The verifier uses constant-time comparison to prevent timing attacks.

GDPR — right to erasure

Superkabe subscribes to HubSpot's contact.privacyDeletion webhook. When a HubSpot user permanently deletes a contact for GDPR reasons, HubSpot fires that event to us, and within seconds we:

  1. Locate every Superkabe lead linked to that HubSpot contact
  2. Cancel any pending activity-push items for those leads (so we never send another note about them to anyone)
  3. Block the lead from outbound sending across all campaigns
  4. Delete the contact-link row that maps Superkabe ↔ HubSpot
  5. Audit-log the deletion with timestamps + masked email fragment

The underlying Superkabe lead is retained but blocked, because the same person may exist in your account from another lawful processing basis (e.g., imported from Apollo separately). For full Lead-level erasure, point your customer at Data Rights which handles GDPR Article 17 across all Superkabe data.

Token storage

  • OAuth access tokens and refresh tokens are encrypted at rest with AES-256 (per the ENCRYPTION_KEY rotated independently from JWT_SECRET).
  • Tokens are decrypted only when the per-org CRM client is instantiated for an actual API call; never written to logs.
  • Disconnect wipes the encrypted blob immediately and cancels pending pushes.
  • Refresh failure → connection marked expired; the user must re-authorize.

Audit trail

Connection events (connected, disconnected, expired, privacy-deleted) are written to the platform's audit log under organization scope. Customers on Growth+ tiers can export this via /dashboard/audit.

Limitations

  • v1 ships activities as Notes. Phase 4 graduates to a registered timeline-event template for richer rendering.
  • Custom Object support is on the roadmap — for now contacts only.
  • Two-way contact creation (Superkabe → HubSpot) is not yet enabled — the integration is read-from / push-activity-to.