You have a website, you have HubSpot, and you want every contact request to reach the CRM with its source, without duplicates and without anyone retyping it. Integrating HubSpot with a website looks simple: some code to paste, a form that appears. The trouble comes later, when the sales team finds contacts with no source, empty fields, or visitors who declined cookies and about whom nothing is known any more.
I'm writing about it because I've done it on client work. I designed and built a B2B website in Webflow connected to HubSpot, with custom JavaScript and GDPR requirements, and on another project I connected HubSpot and Mailchimp. Here is the method I've kept from it, in the order I work.
01Three ways to connect a website to HubSpot
Before talking about code, you need to choose the route. There are three, and they don't come with the same constraints.
The first is the embedded HubSpot form. You build it in HubSpot, copy its embed code and paste it into an HTML block on your page. HubSpot states that to track analytics, the tracking code must also be installed on that page, and that submissions from domains not added to the tracking settings will be marked as spam (HubSpot, Style and embed HubSpot forms on an external site). It's the shortest route, and the one I recommend when the marketing team wants to edit its forms without me.
The second keeps your site's own form and lets the HubSpot tracking code capture it. The conditions are strict: a static HTML form inside a form tag, an email field, no iframe, no script bound to the submission. Forms added after the page has loaded are not captured, and single-page applications have the same problem (HubSpot, Use non-HubSpot forms). I'm wary of it on a Next.js site, where you often move from page to page without a full reload.
The third goes through the forms submission API. The site sends the data to a HubSpot form itself, with a context object (the tracking cookie token, the page address and name) and the consent options (HubSpot Developers, Submit data to a form). It's the route that gives the most control, and the one that needs the most upkeep.
How I choose
- The embedded form, if the site is simple and marketing wants control over the fields.
- Capture, if the site's form already exists, is clean and hardly ever changes.
- The API, if the form has several steps, logic specific to the site or checks that HubSpot doesn't do.
On the Webflow site I mentioned, I added custom JavaScript. That choice has a cost: every line written for the form becomes a line to maintain when HubSpot or the site changes.
02What I write before touching the code
The first document I write is a mapping table. One row per site field, and for each: the label shown, the technical name, the HubSpot property that receives it, the data type, whether it's required, and the expected values when it's a dropdown. It looks like this:
- Work email → "email" property, text, required.
- Company → "company" property, text, required.
- Enquiry type → a custom property, fixed list, required.
- Source page → passed in the context, automatic, required.
This table prevents two classic failures. The first is implicit reference data: an "Enquiry type" dropdown on the site whose values don't exactly match those of the HubSpot property, and contacts arriving with an empty field. The second is a document that changes without everyone knowing. I version this table like an integration specification: every meeting that changes a field produces a new version, and everyone talks about the same document.
One detail to read before choosing capture: HubSpot says that in this mode, fields only match single-line text properties, and the mapping is automatic (HubSpot, Use non-HubSpot forms). If your table contains fixed lists, this route will fight you.
A public example reassured me about this way of working. In a talk on the HubSpot Developers channel, a team shows a multi-screen form in which each field maps one to one to a field of a single HubSpot form. Editors change a label or an option in HubSpot, and the site follows. Submission goes through a server-side function that pushes the data into the CRM, which then triggers the automations (HubSpot Developers, How to Build a Flow Framework with HubSpot Forms API). What I take from it above all is the discipline: one field, one property, and a single source of truth.
03Consent, the part people discover too late
This is the part left out of the quote and discovered during acceptance testing. The HubSpot tracking code sets cookies, including hubspotutk, which keeps track of a visitor's identity. This cookie is passed along with a form submission to link it to the visits, and it's used to deduplicate contacts. HubSpot doesn't class these analytics cookies as strictly necessary: they depend on the consent banner (HubSpot, Cookies set in your visitor's browser by HubSpot).
Under French rules, the CNIL (the French data protection authority) only exempts from consent the trackers used solely to measure audience on the publisher's behalf and that produce anonymous statistical data, with no cross-referencing with other processing (CNIL, Cookies: solutions for audience measurement tools (in French)). Tracking that links visits to a named contact doesn't fall within that scope, in my reading. So I assume the tracking code waits for the visitor's agreement.
The practical consequence is simple, and it often surprises people: a visitor who declines cookies can still submit the form, but their record arrives without their visit history. Better to tell the sales team before go-live than after.
The form itself must also inform people. The CNIL recommends layered information: the basics (who collects, why, which rights) directly on the form, then a link to the full notice (CNIL, GDPR compliance: informing people and ensuring transparency (in French)). I write it into the mapping table, as a field in its own right, so that it doesn't disappear from one version to the next.
04Test as a visitor, not as the team
A lesson I repeat often: a website can look fine to the logged-in team and be broken for visitors. With HubSpot, the team is logged into the CRM, already has its cookies and has usually accepted the banner long ago. They don't see what a stranger sees.
My test list before go-live:
- Open the site in a private window, decline cookies, submit the form, then check that the contact still reaches HubSpot.
- Do it again accepting cookies, and check that the record shows the source page and the visits.
- Submit twice with the same email address, and check there is no duplicate.
- Test every value of every fixed list in the table, one by one.
- Submit from a page reached through an internal link, without reloading, to check the form is still captured.
- Check on mobile that the consent block and the submit button stay visible.
I keep these tests in the same list as the rest of the acceptance testing before go-live. They take an hour and they spare you weeks of badly filed contacts.
05What this integration doesn't fix
A clean integration doesn't replace a clean database. If HubSpot already contains duplicates, properties created twice by three different people or lists that have never been cleaned, connecting the site to it amplifies the mess. In that case I start with the clean-up, and I say so before starting.
It doesn't fix measurement either. Knowing which campaigns bring in enquiries takes consistent tracking between the site, the analytics tool and the CRM, then someone who reads it every month. I cover that in the piece on automated SEO reporting.
And I don't push the API on principle. It gives control, but it creates code to maintain, credentials to protect and errors to monitor: the authenticated API returns 401 when the credentials are wrong and 403 when the account lacks permission to submit (HubSpot Developers, Submit data to a form). If nobody watches those responses, enquiries get lost silently. For many SMEs the embedded form is enough, and that's perfectly fine.
Finally, if you're planning to rebuild the site, do the integration with the redesign rather than before it. When I prepare a migration to Next.js and Sanity, forms are part of the inventory, just like pages.
06Where to start this week
Open your site in a private window, decline cookies and submit your own form. Then look at what arrives in HubSpot: the record, the source, the fields. That ten-minute test will tell you whether you have a problem.
If you do, write the mapping table before touching the code, then choose the route. It's the work I do as a diagnostic at the start of a project, and the table will stay useful even if you change tools one day. If you'd like us to look at your case together, my services page describes how I work, and we can talk it through.
