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, andemail.bouncedevent from Superkabe lands as a Note on the matching HubSpot contact's timeline. - Suppression sync. HubSpot contacts with
hs_email_optout=trueare 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
- Sign in at developers.hubspot.com with the developer account that should own the listing.
- Apps → Create app → Public app.
- Name it "Superkabe". Add a logo + description.
- Auth tab → set Redirect URL to:
https://api.superkabe.com/api/integrations/hubspot/callback - Add OAuth scopes:
oauthcrm.objects.contacts.readcrm.objects.contacts.writecrm.lists.readcrm.schemas.contacts.readtimeline
- 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.
- 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.
- 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.
- 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.
- 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
429when daily/burst limits are hit. The Superkabe client honorsRetry-Afterautomatically (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
failedand 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:
- Locate every Superkabe lead linked to that HubSpot contact
- Cancel any pending activity-push items for those leads (so we never send another note about them to anyone)
- Block the lead from outbound sending across all campaigns
- Delete the contact-link row that maps Superkabe ↔ HubSpot
- 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_KEYrotated independently fromJWT_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.