Skip to main content

Embedding a HubSpot v4 Form via Iframe

User story: As a support team, when a visitor tells the copilot they need help with a product, we want the actual HubSpot form to render inline in the chat, not a link out to it, so the submission goes straight into our existing HubSpot form pipeline without the visitor ever leaving the conversation.

ai12z has an older approach that injects HubSpot's script directly into the chat bubble's Shadow DOM (see Embedding a HubSpot Form via Custom Integrations), but it only works with HubSpot's older v2 embed script. HubSpot's newer v4 embed forms load additional scripts and stylesheets that assume they're running against the top-level page, not a fragment relocated inside a Shadow Root, so that technique does not reliably work for them. If your form uses HubSpot's v4 embed code, this guide is the one to use.

Instead of injecting anything into the Shadow DOM, you host a small standalone page that loads HubSpot's v4 embed script, then point a Template (HTML) integration at that page with a plain <iframe>. Because the form lives on its own page in its own document, there's no Shadow DOM to reach into and nothing to relocate: HubSpot's v4 script, its stylesheets, and any supporting scripts it loads all run inside the iframe's own document, exactly as HubSpot intends.


Prerequisites

  • A form already built in HubSpot Forms, with its Portal ID, Form ID, and region (e.g. na1) from the form's v4 embed code in HubSpot
  • Somewhere public to host a static HTML page per form (your website, a CDN, GitHub Pages, S3, etc.)
  • Access to your agent's Integrations tab in ai12z

Step 1: Host a Page for the Form

For each HubSpot form you want to embed, create and publish a small standalone HTML page. This example uses hubspotform-v4.html:

<!DOCTYPE html>
<html>
<head>
<title>Bot</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script
type="text/javascript"
src="https://js.hsforms.net/forms/embed/developer/YOUR_HUBSPOT_PORTAL_ID.js"
defer
></script>
</head>
<body>
<div
class="hs-form-html"
data-portal-id="YOUR_HUBSPOT_PORTAL_ID"
data-form-id="YOUR_HUBSPOT_FORM_ID"
data-region="na1"
></div>
</body>
</html>

Replace YOUR_HUBSPOT_PORTAL_ID, YOUR_HUBSPOT_FORM_ID, and the data-region value with the ones from that specific form's own v4 embed code in HubSpot. Publish this page to a public URL: it's what the iframe in Step 4 will load.

important

The page's URL has to be publicly reachable. A localhost or local dev-server URL (e.g. http://127.0.0.1:5500/...) works fine while you're building and testing on your own machine, but the bot runs in visitors' browsers, not yours, so it needs the real published URL before this goes live.


Step 2: Create the Custom Integration

Go to Integrations → Custom Integrations and click Create Integration. Set the properties:

FieldValue in This ExampleWhy
NameHubspot V4 integrationInternal, human-readable identifier
Description"Call this integration anytime someone says Test Hubspot V4"This is the trigger condition, see below
Data SourceNoneThe widget doesn't fetch external data; it just loads the hosted page in an iframe
Handle ResponseTemplate (HTML)Renders a custom HTML widget in the chat instead of a plain LLM text reply
Custom FunctionUncheckedNo Python post-processing needed for a static iframe
important

Same rule as any custom integration: the Description is the trigger, not documentation. ai12z's Reasoning LLM reads it to decide whether and when to call the integration during a live conversation. The description used above ("Call this integration anytime someone says Test Hubspot V4") is a test-phase trigger, useful while you're building this. For production, write a specific, natural description of when a real visitor would need this form, e.g. "When a customer needs help with a product, we ask them to fill out this form", the same way Embedding a HubSpot Form via Custom Integrations documents it.

Click Next to move into the integration editor.


Step 3: Point the Template at the Hosted Page

The editor opens to the Template tab. Leave Styles and Scripts empty, and paste an iframe into HTML pointing at the page you published in Step 1:

<div class="frame-shell">
<iframe
id="contentFrame"
width="100%"
height="700px"
title="Embedded content"
loading="lazy"
src="https://your-domain.com/hubspotform-v4.html"
></iframe>
</div>

Template tab showing the frame-shell div and iframe pointed at the hosted HubSpot page

The frame-shell class is just a wrapper; it doesn't need any CSS since nothing in Styles references it. The one value worth tuning is height: set it to match the actual rendered height of the form on its hosted page, not an arbitrary guess. Open the published page directly in a browser, see how tall the form actually renders, and set height accordingly, otherwise the form either gets clipped or leaves extra empty space inside the chat bubble.


Step 4: Test It

Click Preview in the Template tab. Unlike the script-based approach, Preview here renders the live HubSpot form directly, since the iframe just loads a real page. If it doesn't render, open the hosted page's URL directly in a browser tab first to confirm the page itself loads and the form appears there before troubleshooting the integration.

Then open Test Drive on the agent and type something that matches the Description's trigger condition:

User: "Test Hubspot V4"
Agent: (renders the embedded HubSpot form inline, instead of a text reply)

HubSpot form rendered inline inside the ai12z bot conversation


How This Differs from the Script-Based Embed

This Guide (Iframe)Script-Based Embed
HubSpot embed versionv4v2 only
What rendersHubSpot's form inside an iframe, loaded from a page you hostHubSpot's form injected directly into the chat bubble's own DOM
SetupHost one small static HTML page per form; paste an iframe tagPaste a JavaScript block that reaches into the Shadow DOM and relocates nodes
Preview tabRenders the live form directlyShows nothing; it's a pure JavaScript template
HeightFixed; set manually to match the formGrows and shrinks with the actual form content
Best forAny HubSpot v4 form. Also a good default for v2 forms if you'd rather not host a workaround scriptv2 forms only, where you specifically want the form to feel fully native inside the chat bubble

Check which embed version your form actually uses before picking a guide: HubSpot's v2 embed code calls hbspt.forms.create(...) from a script tag pointing at forms/embed/v2.js; v4's embed code is a script tag pointing at forms/embed/developer/<portal_id>.js alongside a <div class="hs-form-html" ...>, with no hbspt.forms.create() call. That's the code HubSpot gives you when you click Share or Embed on the form; if you're not sure, copy it from there rather than guessing.

Neither approach pushes data into HubSpot's CRM directly through ai12z; both just render HubSpot's own form, which still submits to HubSpot the normal way. For pushing conversation data into HubSpot's CRM without rendering a visible form at all, see HubSpot Contact Integration.