Orakel Docs
Integrations

HubSpot

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

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.

FieldHubSpot propertyTypeCountries
Org numberorg_numberCustom textAll — required, always pushed
Company namenameBuilt-inAll
EmployeesnumberofemployeesBuilt-inAll
Org formorg_formCustom textAll
Industry (NACE)nace_industryCustom textAll
RevenueannualrevenueBuilt-inAll
Operating profitoperating_result_nokCustom numberAll
Equitytotal_equity_nokCustom numberAll
Total assetstotal_assets_nokCustom numberAll
CEO nameceo_nameCustom textAll
Board chairboard_chairCustom textAll
Board membersboard_membersCustom textAll
WebsitewebsiteBuilt-inAll
Tech stacktech_stackCustom textAll
LinkedInlinkedin_company_pageBuilt-inAll
Active tendersactive_tendersCustom numberNO only
Latest tender URLlatest_tender_urlCustom textNO only
Street addressaddressBuilt-inAll
Postal codezipBuilt-inAll
MunicipalitycityBuilt-inAll

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, reconnect:

  1. Go to orakel.cloud/account?tab=integrations.
  2. Click Disconnect next to your HubSpot connection.
  3. Click Connect HubSpot and complete the OAuth flow again.
  4. Orakel will update your existing connection — no data is lost and no duplicate portal is created.

Orakel card missing from company records — The Orakel card shows enriched data directly inside HubSpot company records. If it disappears, it has likely been removed from the record layout. To restore it:

  1. Open any Company record in HubSpot.
  2. Click Customize (pencil icon, top right of the right-hand panel).
  3. Under Cards, find Orakel in the list of available cards and drag it into the layout.
  4. Click Save.

Alternatively, to restore it for all users at once: go to Settings → Objects → Companies → Record Customization, select the default view, and add the Orakel card there.

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".

On this page