orakel
Docs navigation

Canonical

HubSpot

Push Orakel company records into HubSpot via OAuth. Self-serve install with automatic trial key creation.

Updated 2026-05-20

Overview

HubSpot is a CRM. Orakel installs as an OAuth app inside a HubSpot portal. Two flows run once installed:

  • Webhook enrichment: when a Company is created or org_number changes, HubSpot calls Orakel; Orakel fetches the enriched record (Brreg, Regnskapsregisteret, roles, domains) and PATCHes the company back.
  • Batch push: triggered from the Integrations tab in your account (/account?tab=integrations) or via POST /api/account/integrations/hubspot/sync.

OAuth tokens and the portal ID are stored as a Destination of type hubspot, encrypted at rest (AES-256-GCM). Access tokens refresh automatically on 401.

Setup

No invite required. HubSpot is self-serve.

  1. Go to your account: orakel.cloud/account?tab=integrations. Click Connect HubSpot.
  2. Authorize. Tick "I understand the risks of connecting an unverified app" and click Connect app.
  3. Return to the Integrations tab. Orakel has provisioned custom properties on your Company object and will start enriching companies that have org_number set. A 7-day trial API key is created automatically if you don't already have one.
  4. Test. Open any Company record in HubSpot, set Organisasjonsnummer to a 9-digit org number (e.g. 923609016), save. Within 5–10 seconds Orakel fills in name, address, NACE, employees, revenue, CEO, and more.
  5. Adjust fields. From the Integrations tab, tick or untick which fields Orakel pushes. Click Save changes, then Sync now to re-enrich all companies.

OAuth flow

  • GET /api/oauth/hubspot/install sets a state cookie and redirects to https://app-eu1.hubspot.com/oauth/authorize.
  • GET /api/oauth/hubspot/callback exchanges the code, introspects portalId and installer email, upserts a Destination named hubspot-<portalId>, and provisions custom properties. A trial API key + magic-link welcome email are sent when no key exists for the installer.
  • Refresh tokens are rotated on every refresh; lib/hubspot/client.ts handles it on 401.

Field mapping

Fields are controlled per-portal from the Integrations tab. All fields write to HubSpot Company objects. Custom properties are provisioned on first install.

Field HubSpot property Type Countries
Org number org_number Custom text All — required, always pushed
Company name name Built-in All
Employees numberofemployees Built-in All
Org form org_form Custom text All
Industry (NACE) nace_industry Custom text All
Revenue annualrevenue Built-in All
Operating profit operating_result_nok Custom number All
Equity total_equity_nok Custom number All
Total assets total_assets_nok Custom number All
CEO name ceo_name Custom text All
Board chair board_chair Custom text All
Board members board_members Custom text All
Website website Built-in All
Tech stack tech_stack Custom text All
LinkedIn linkedin_company_page Built-in All
Active tenders active_tenders Custom number NO only
Latest tender URL latest_tender_url Custom text NO only
Street address address Built-in All
Postal code zip Built-in All
Municipality city Built-in All

Null values are dropped before PATCH.

Configuration

The callback writes this config shape (encrypted) — you do not author it by hand:

{
  "accessToken": "...",
  "refreshToken": "...",
  "expiresAt": 1713705600000,
  "portalId": 147637517,
  "userEmail": "installer@acme.example",
  "fieldSelection": ["companyName", "employeeCount", "revenue", "ceoName", "website", "streetAddress", "postalCode"]
}
  • portalId is the HubSpot hub ID; used as the match key when looking up the destination by portal.
  • fieldSelection is the list of slugs selected from the Integrations tab. Defaults to the 7 fields listed above.

Push behavior

  • Match: search /crm/v3/objects/companies/search for org_number = <orgNumber>, limit: 1.
  • Update: PATCH /crm/v3/objects/companies/<id> with the selected properties.
  • Create: POST /crm/v3/objects/companies with the same properties.
  • Webhook: company.creation and company.propertyChange (property org_number) subscriptions trigger enrichHubspotCompany(portalId, objectId) asynchronously. The webhook endpoint returns 200 { ok: true, processing: [...] } immediately.
  • Signature: v3 HMAC-SHA256 over METHOD + public URI + body + timestamp. The public URI is reconstructed from X-Forwarded-Proto and X-Forwarded-Host so signing matches behind the reverse proxy.

Common errors

Unlisted-app warning at install — HubSpot shows a warning saying the app is unreviewed. This is expected during pre-launch. Click Connect app to proceed.

Fields not enriched after install — Check that org_number is set on the company record. Enrichment fires when org_number is created or updated. For existing companies, use Sync now from the Integrations tab.

400 INVALID_OPTION on industry — HubSpot's built-in industry field only accepts one of 147 predefined values. Orakel writes Norwegian NACE data to a separate custom field (nace_industry) and leaves HubSpot's built-in industry untouched.

Token expired / reconnect needed — OAuth access tokens refresh automatically on every use. If you see persistent authentication errors, disconnect from the Integrations tab and reconnect.

Procurement fields not appearing — Active tenders and latest tender URL are Norway-only fields. They appear in the field selector only when the country filter is set to NO or All.

Gotchas

  • industry is a 147-value enum. Pushing Norwegian NACE text returns 400 INVALID_OPTION. Orakel writes NACE to a custom nace_industry text field; HubSpot's built-in industry is untouched.
  • Boolean properties need explicit options. is_bankrupt is provisioned with the true/false pair or HubSpot returns INVALID_BOOLEAN_OPTION.
  • Brand names preserved — webhook enrichment does not overwrite name on existing records.
  • Unlisted-app warning at install is expected. The app is unreviewed, not unsafe.
  • One destination per portal. Tokens are portal-scoped. Re-installing the same portal updates the existing destination.
  • Sync is on-demand. From the Integrations tab, click Sync now to re-enrich all companies. No time limit.
  • Procurement fields (active tenders, latest tender URL) are Norway-only. They appear in the field selector only when the country filter is set to "NO" or "All".