{ "hubspot" :

Enrich HubSpot companies with Dutch registry data

Short answer

HubSpot has no KVK field and does not enrich from the Dutch trade register. You create custom properties on the Company object and fill them from a workflow webhook action, a coded action, or a private app listening to CRM webhooks. We do not ship a HubSpot app: you call our REST API from that middleware.

HubSpot API
# write the enrichment back
PATCH "/crm/v3/objects/companies/512"

{
  "properties": {
    "name": "Acme B.V.",
    "kvk_number": "68750110",
    "sbi_code": "6201",
    "vat_valid": "true"
  }
}

The properties you create

HubSpot ships name, domain, city and numberofemployees. Those hold whatever sales typed or whatever HubSpot inferred from an email domain. Registry data does not belong in the same fields. Create your own properties with a clear provenance.

PropertyTypeFrom the API
kvk_numbersingle-line textkvkNumber
statutory_namesingle-line textstatutoryName
legal_formdropdownlegalForm
sbi_codesingle-line textactivities[].sbiCode where isMain
vat_numbersingle-line textvat.number
vat_validbooleanvat.valid
company_activebooleanisActive
enriched_atdate pickertime of the call

enriched_at looks like overhead until someone asks how old the data is. Without a timestamp you cannot re-enrich selectively, so you re-enrich everything every month and walk straight into your quota.

The integration point: workflow, coded action or private app

There are three places the call can originate, and they differ in cost and in what they can handle.

Workflow with a webhook action. Enroll companies where kvk_number is known and enriched_at is empty. HubSpot POSTs to your endpoint, you perform the lookup and write back through the CRM API. Simple, and your logic lives in your own code.

Coded action. Runs inside HubSpot, no server of your own. Requires Operations Hub Professional and inherits that runtime’s execution limits. Fine for one lookup per record, wrong for a backfill.

Private app on CRM webhooks. Subscribe to company.propertyChange for kvk_number. Enrichment then happens the moment the number changes, on any tier. The payload is an array of events:

[
  {
    "subscriptionType": "company.propertyChange",
    "objectId": 512,
    "propertyName": "kvk_number",
    "propertyValue": "68750110",
    "occurredAt": 1755820800000
  }
]

Process that array asynchronously. HubSpot batches events and expects a fast 2xx: a handler that blocks on our API during a spike runs into timeouts, and HubSpot then redelivers the same event.

Write back in batches, not per record:

POST /crm/v3/objects/companies/batch/update

with at most a hundred records per call. On our side that pairs with POST /v1/lookup/batch and a body of { "kvkNumbers": [...], "enrich": true }. Both sides stay inside their limits.

Where the input comes from

The KVK number has to enter the CRM somehow. Two routes:

  • Your own form, with our widget attached to the input. The visitor searches by name, picks a company, and you submit the KVK number to the HubSpot Forms API.
  • Manual entry by sales on the company record. That needs validation afterwards, because a hand-typed number is exactly as reliable as retyping implies.

What does not work: attaching the widget to an embedded HubSpot form that renders inside an iframe. Your script cannot reach that input. Check how your embed renders before promising autocomplete on a HubSpot form. If it is an iframe, build the form yourself and post to the Forms API.

Either way, write the number into a property that only your integration touches. A field edited by both sales and a sync turns into an argument about which value is correct, and the register always loses that argument to whoever typed last.

What actually breaks in production

Domain deduplication collapses your group structure. HubSpot uses the domain as the key for automatic association. A holding, its operating company and a franchise location often share one website. HubSpot ends up with one company where you have three KVK numbers. Key your own dedupe on kvk_number, not on domain, and accept that you sometimes want two HubSpot records on the same domain.

The workflow triggers itself. Enroll on “kvk_number is known”, then write properties back to the same record, and it re-enrolls on the next change. Turn re-enrollment off explicitly and filter on enriched_at is unknown.

A company returns 404. Deregistered entries leave the register. Treat NOT_FOUND as information rather than an error: set company_active to false and enriched_at to now, or your workflow retries it on every run.

The first backfill eats your monthly quota. Enriching five thousand companies is five thousand lookups. Filter down to the records where it actually changes something and spread the rest. HubSpot meters its own limit per ten seconds, so batches of a hundred records per call keep both sides calm.

For the wider approach to CRM enrichment there is a longer piece in CRM company data enrichment.

Frequently asked

Do you have a HubSpot marketplace app?
No. We ship a REST API. You connect it with a private app or a workflow webhook pointing at your own endpoint. That is a small piece of middleware which performs the lookup and writes the result back through the CRM API.
Which HubSpot tier do I need?
The webhook action in workflows starts at Professional, and the coded action requires Operations Hub Professional. On Starter you listen to CRM webhooks from a private app, or run a periodic sync that pulls the list of companies to enrich itself.
Which field should I key on?
The KVK number, in a custom property. Not the company name: trade name and statutory name almost always differ, and fuzzy matching returns the wrong company. Without a KVK number you need a search step first.
Will enrichment overwrite what sales typed in?
Only if you build it that way. Write registry data to your own properties such as statutory_name and leave the free-text fields alone. That keeps it visible which values came from the register and which a human entered.

Updated:

One call gives you the whole company

KVK data, the derived VAT number and a live VIES check, in a single request. The free plan covers 50 lookups a month and needs no card.

50
free lookups a month
1
request instead of three
0
cards, contracts or sales calls