Canonical
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_numberchanges, HubSpot calls Orakel; Orakel fetches the enriched record (Brreg, Regnskapsregisteret, roles, domains) andPATCHes the company back. - Batch push: triggered from the Integrations tab in your account (
/account?tab=integrations) or viaPOST /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.
- Go to your account: orakel.cloud/account?tab=integrations. Click Connect HubSpot.
- Authorize. Tick "I understand the risks of connecting an unverified app" and click Connect app.
- Return to the Integrations tab. Orakel has provisioned custom properties on your Company object and will start enriching companies that have
org_numberset. A 7-day trial API key is created automatically if you don't already have one. - Test. Open any Company record in HubSpot, set
Organisasjonsnummerto a 9-digit org number (e.g.923609016), save. Within 5–10 seconds Orakel fills in name, address, NACE, employees, revenue, CEO, and more. - 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/installsets a state cookie and redirects tohttps://app-eu1.hubspot.com/oauth/authorize.GET /api/oauth/hubspot/callbackexchanges the code, introspectsportalIdand installer email, upserts aDestinationnamedhubspot-<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.tshandles it on401.
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_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"]
}portalIdis the HubSpot hub ID; used as the match key when looking up the destination by portal.fieldSelectionis the list of slugs selected from the Integrations tab. Defaults to the 7 fields listed above.
Push behavior
- Match: search
/crm/v3/objects/companies/searchfororg_number = <orgNumber>,limit: 1. - Update:
PATCH /crm/v3/objects/companies/<id>with the selected properties. - Create:
POST /crm/v3/objects/companieswith the same properties. - Webhook:
company.creationandcompany.propertyChange(propertyorg_number) subscriptions triggerenrichHubspotCompany(portalId, objectId)asynchronously. The webhook endpoint returns200 { ok: true, processing: [...] }immediately. - Signature: v3 HMAC-SHA256 over
METHOD + public URI + body + timestamp. The public URI is reconstructed fromX-Forwarded-ProtoandX-Forwarded-Hostso 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
industryis a 147-value enum. Pushing Norwegian NACE text returns400 INVALID_OPTION. Orakel writes NACE to a customnace_industrytext field; HubSpot's built-inindustryis untouched.- Boolean properties need explicit options.
is_bankruptis provisioned with thetrue/falsepair or HubSpot returnsINVALID_BOOLEAN_OPTION. - Brand names preserved — webhook enrichment does not overwrite
nameon 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".
Related
- Destinations endpoint — CRUD for destinations and push trigger
- Brreg — primary source for firmographics and roles
- Regnskapsregisteret — source for the financial fields