Integrate Bolt.new with HubSpot Marketing Hub by creating a private app access token in your HubSpot portal, storing it in your .env file, and calling marketing-specific API endpoints through server-side Next.js routes. Build email campaign analytics dashboards, track form submission conversion rates, and surface campaign ROI data. All data-fetching API calls work in Bolt's WebContainer preview; HubSpot webhook subscriptions require a deployed public URL.
Building a HubSpot Marketing Analytics Dashboard in Bolt.new
HubSpot Marketing Hub sits on top of the core HubSpot CRM and adds email marketing, landing pages, forms, SEO tools, and marketing automation. The key insight for this integration is that the same HubSpot private app access token used for CRM operations (contacts, deals) also provides access to Marketing Hub endpoints — there is no separate API credential to manage. If your team already has a HubSpot private app for CRM work, you can extend its scopes to include marketing data without creating a new app.
The most valuable Marketing Hub endpoints for Bolt dashboards are the Email Statistics API (open rates, click rates, bounce rates, and revenue attribution per campaign), the Forms API (submission counts and field data per form), and the Analytics API (page views, sessions, and traffic sources). These three endpoints together let you build a complete marketing performance dashboard that sales and marketing teams can use to see which campaigns and pages are driving the most qualified leads. HubSpot's attribution models can show which marketing touchpoints contributed to closed deals — surfacing this in a custom Bolt dashboard is significantly more flexible than the built-in HubSpot reports.
All Marketing Hub API calls are outbound HTTPS requests, which means they work perfectly in Bolt's WebContainer development environment. You can build, test, and refine the entire dashboard without deploying. The one exception is HubSpot webhook subscriptions — if you need real-time notifications when a form is submitted or a contact's lifecycle stage changes, HubSpot needs to POST events to a publicly accessible URL, which requires deploying to Netlify, Vercel, or Bolt Cloud first. For read-only analytics dashboards, deployment is optional until you are ready to share with your team.
Integration method
Bolt generates the HubSpot Marketing Hub integration through conversation — describe the marketing analytics features you need and Bolt writes the API routes and dashboard components. The same private app access token used for HubSpot CRM also works for Marketing Hub endpoints, so there is no additional OAuth setup. All API calls are proxied through server-side Next.js routes to keep the access token out of the client bundle. HubSpot webhook subscriptions for real-time marketing events require a deployed public URL since Bolt's WebContainer cannot receive incoming connections.
Prerequisites
- A HubSpot account with Marketing Hub access — the free tier includes basic forms and email; paid plans add campaign analytics
- A HubSpot private app created in Settings → Integrations → Private Apps with marketing-scoped access token
- The private app access token from the HubSpot private app setup (displayed once at creation, can be rotated in settings)
- A Next.js project in Bolt.new — prompt 'Create a Next.js app' if starting fresh
- For webhook subscriptions: a deployed app with a public HTTPS URL (Netlify or Vercel recommended)
Step-by-step guide
Create a HubSpot private app with marketing scopes
Create a HubSpot private app with marketing scopes
Log in to your HubSpot portal and navigate to Settings (the gear icon in the top navigation). In the left sidebar, scroll to Integrations → Private Apps. Click Create a private app. Give the app a name like 'Bolt Marketing Dashboard' and add a description. Click the Scopes tab. For Marketing Hub analytics, you need these scopes: crm.objects.contacts.read (to read contact and lead data), content (to access email campaigns and landing page data), forms (to read form submissions and form definitions), and analytics.behavioral_events.send (if you plan to track custom events). Toggle each scope to Read access. The scopes you select determine which API endpoints your token can access — a token without the content scope cannot read email campaign statistics. Click Create app in the top right. HubSpot will display your access token — a string starting with pat-. Copy it immediately and store it in a safe place. This token is shown once in full; if you navigate away, you can only view the last four characters. You can rotate (regenerate) the token at any time from the private app settings page. In your Bolt project, open or create the .env.local file and add the token as HUBSPOT_PRIVATE_TOKEN. This is a server-side-only variable — never prefix it with NEXT_PUBLIC_ as that would expose it in the browser. All HubSpot API requests authenticate using this token in an Authorization: Bearer header.
Set up HubSpot Marketing Hub integration in my Next.js app. Add HUBSPOT_PRIVATE_TOKEN to .env.local as a placeholder. Create lib/hubspot.ts with a hubspotFetch helper function that accepts an endpoint path and optional query parameters, adds the Authorization: Bearer header, and returns the parsed JSON response. The base URL is https://api.hubapi.com.
Paste this in Bolt.new chat
1// .env.local2HUBSPOT_PRIVATE_TOKEN=pat-na1-your-token-here34// lib/hubspot.ts5export async function hubspotFetch(6 endpoint: string,7 options: { method?: string; body?: unknown } = {}8): Promise<Response> {9 const token = process.env.HUBSPOT_PRIVATE_TOKEN!;10 const url = `https://api.hubapi.com${endpoint}`;1112 return fetch(url, {13 method: options.method ?? 'GET',14 headers: {15 Authorization: `Bearer ${token}`,16 'Content-Type': 'application/json',17 },18 ...(options.body ? { body: JSON.stringify(options.body) } : {}),19 });20}Pro tip: HubSpot private app tokens are scoped to a specific portal and do not expire, but they can be rotated in Settings → Private Apps if compromised. Rotating generates a new token and immediately invalidates the old one — update your .env.local after rotation.
Expected result: Your HubSpot private app appears as Active in HubSpot Settings, the access token is stored in .env.local, and the hubspotFetch utility is ready to use in API routes.
Fetch email campaign statistics and analytics
Fetch email campaign statistics and analytics
The HubSpot Marketing Email Statistics API provides detailed performance data for every email campaign sent through Marketing Hub. The primary endpoint is GET /marketing/v3/emails/{emailId}/statistics/histogram for time-series data, or GET /marketing/v3/emails/{emailId}/statistics/list for a flat summary per email. To list all marketing emails and their summary statistics, call GET /marketing/v3/emails with query parameters including limit (max 100), after (cursor for pagination), and optionally archived=false to only show active emails. Each email object includes the campaign ID, name, subject line, send time, and a counters object with delivered, open, click, bounce, and unsubscribed counts. Calculate open_rate = opens / delivered and click_rate = clicks / delivered in your API route before returning data to the frontend. For more detailed analytics including revenue attribution (requires HubSpot Revenue Attribution feature on paid plans), use the Analytics API v2 at /analytics/v2/reports/email-campaigns/. This provides breakdowns by date range, filtered by campaign type. All these GET requests are outbound calls that work perfectly from Bolt's WebContainer. You can build and test the full email analytics dashboard without deploying. Use Bolt's AI to scaffold the charting components — describe the visualization you want (bar chart of open rates by campaign, time-series of email sends per week) and Bolt will generate Recharts or Chart.js components that consume your API route data.
Create the email campaign analytics API route. Build app/api/hubspot/emails/route.ts that fetches the latest 20 HubSpot marketing emails with their statistics. For each email, return: id, name, subject, sendDate, totalSent, openRate (opens/delivered), clickRate (clicks/delivered), bounceRate, and unsubscribeRate. Also build an EmailCampaignsTable component that displays this data with percentage formatting.
Paste this in Bolt.new chat
1// app/api/hubspot/emails/route.ts2import { NextResponse } from 'next/server';3import { hubspotFetch } from '@/lib/hubspot';45export async function GET() {6 // First get list of emails7 const listResp = await hubspotFetch(8 '/marketing/v3/emails?limit=20&archived=false'9 );10 if (!listResp.ok) {11 return NextResponse.json({ error: 'HubSpot API error' }, { status: listResp.status });12 }13 const listData = await listResp.json();1415 // Fetch stats for each email16 const emails = await Promise.all(17 (listData.results ?? []).map(async (email: Record<string, unknown>) => {18 const statsResp = await hubspotFetch(19 `/marketing/v3/emails/${email.id}/statistics/list`20 );21 const statsData = statsResp.ok ? await statsResp.json() : {};22 const counters = (statsData.counters ?? {}) as Record<string, number>;23 const delivered = counters.delivered ?? 1;2425 return {26 id: email.id,27 name: email.name,28 subject: email.subject,29 sendDate: email.publishDate,30 totalSent: counters.sent ?? 0,31 openRate: Math.round(((counters.open ?? 0) / delivered) * 10000) / 100,32 clickRate: Math.round(((counters.click ?? 0) / delivered) * 10000) / 100,33 bounceRate: Math.round(((counters.hardbounced ?? 0) / delivered) * 10000) / 100,34 unsubscribeRate: Math.round(((counters.unsubscribed ?? 0) / delivered) * 10000) / 100,35 };36 })37 );3839 return NextResponse.json({ emails, total: listData.total });40}Pro tip: HubSpot's email statistics API applies rate limits at 100 requests per 10 seconds. If you fetch stats for many emails in parallel, use Promise.all with a maximum of 5 concurrent requests to avoid 429 errors.
Expected result: GET /api/hubspot/emails returns a list of email campaigns with calculated open rates, click rates, bounce rates, and unsubscribe rates formatted as percentages.
Build the forms analytics and submission tracking endpoint
Build the forms analytics and submission tracking endpoint
The HubSpot Forms API provides access to all form definitions and their submission history. This is one of the highest-value endpoints for marketing analytics — forms are the primary mechanism for lead capture, and understanding which forms convert visitors to leads most effectively directly informs content and campaign strategy. To list all forms, call GET /forms/v2/forms. Each form object includes its guid (the form identifier), name, method (embed or new page), createdAt, updatedAt, and formFieldGroups (the fields configuration). Form submissions are accessed via GET /form-integrations/v1/submissions/forms/{formGuid}?limit=50. The submissions endpoint returns individual submission records with each field value, the page URL where the form was submitted, the submission timestamp, and the associated contact ID if HubSpot matched the submitter to an existing contact. For conversion rate analysis, you'll need to correlate form submissions with page views. The HubSpot Analytics API provides page view data at GET /analytics/v2/reports/pages/ but requires additional scopes. A practical alternative for dashboards is to show submission volume over time — a bar chart of weekly or monthly form submissions per form is highly actionable without requiring page view data. Custom submission handlers are also possible: if your Bolt app uses its own HTML forms (not HubSpot-embedded forms), you can POST submissions to HubSpot's non-HubSpot forms endpoint at POST /submissions/v3/submissions/definition/{portalId}/{formGuid}. This lets you capture leads through a Bolt-designed form while storing the data in HubSpot.
Add HubSpot forms analytics to my dashboard. Create app/api/hubspot/forms/route.ts that fetches all HubSpot forms with their submission counts from the last 30 days. Return form name, guid, total submissions, last submission date, and the page URL of the most recent submission. Build a FormsTable component showing this data sorted by submission count descending.
Paste this in Bolt.new chat
1// app/api/hubspot/forms/route.ts2import { NextResponse } from 'next/server';3import { hubspotFetch } from '@/lib/hubspot';45export async function GET() {6 const formsResp = await hubspotFetch('/forms/v2/forms');7 if (!formsResp.ok) {8 return NextResponse.json({ error: 'HubSpot Forms API error' }, { status: formsResp.status });9 }10 const forms = await formsResp.json() as Array<Record<string, unknown>>;1112 // For each form, get submission count from last 30 days13 const formsWithStats = await Promise.all(14 forms.slice(0, 20).map(async (form) => {15 const guid = form.guid as string;16 const subsResp = await hubspotFetch(17 `/form-integrations/v1/submissions/forms/${guid}?limit=50`18 );19 const subsData = subsResp.ok ? await subsResp.json() : { results: [], total: 0 };20 const submissions = subsData.results as Array<Record<string, unknown>>;21 const lastSubmission = submissions[0];2223 return {24 guid,25 name: form.name,26 createdAt: form.createdAt,27 totalSubmissions: subsData.total ?? submissions.length,28 lastSubmissionAt: lastSubmission?.submittedAt ?? null,29 lastSubmissionPage: lastSubmission?.pageUrl ?? '',30 };31 })32 );3334 formsWithStats.sort((a, b) => (b.totalSubmissions as number) - (a.totalSubmissions as number));35 return NextResponse.json({ forms: formsWithStats });36}Pro tip: If you are building a custom form in Bolt and want to submit it to HubSpot, use the Non-HubSpot forms submission API: POST /submissions/v3/submissions/definition/{portalId}/{formGuid}. This lets you use your own styled form while capturing leads in HubSpot.
Expected result: GET /api/hubspot/forms returns a list of all HubSpot forms sorted by submission count, with each entry showing the total submission count and the most recent submission's page URL.
Build the marketing performance dashboard UI
Build the marketing performance dashboard UI
With the email statistics and forms analytics API routes complete, use Bolt's AI to compose the full marketing performance dashboard. Describe the layout in a Bolt chat prompt: the dashboard should have a top-level summary section showing total leads this month, average email open rate, and best-performing campaign, followed by two sections — a campaigns table and a forms analytics table. For the summary metrics, make a parallel fetch of both the /api/hubspot/emails and /api/hubspot/forms endpoints on page load and compute aggregate statistics client-side: sum up total form submissions across all forms for total leads, average the open rates across all campaigns for average open rate, and find the campaign with the highest open rate for best performer. For charts, Bolt works well with the Recharts library (built on D3.js). Ask Bolt to add a BarChart showing email open rates per campaign with the campaign name on the x-axis and open rate percentage on the y-axis. For forms, a horizontal bar chart comparing submission counts across forms is effective. Both charts consume the data already returned by your API routes — no additional API calls needed. All data fetching uses server-side routes that call HubSpot's API through your Next.js backend, so there are no CORS issues in Bolt's WebContainer. The React components receive JSON data as props and render the UI. This pattern works identically in development preview and after deployment — the only difference is that the deployed version is accessible to your whole team via its public URL.
Build the complete marketing performance dashboard. Create a Dashboard page at /marketing that fetches data from /api/hubspot/emails and /api/hubspot/forms in parallel. Show: (1) Three summary cards: Total Leads This Month, Average Email Open Rate, Best Campaign. (2) An EmailCampaignsChart using Recharts BarChart showing top 10 campaigns by open rate. (3) The FormsTable showing all forms sorted by submissions. Add a refresh button that re-fetches all data.
Paste this in Bolt.new chat
1// app/marketing/page.tsx2import { Suspense } from 'react';34async function getMarketingData() {5 const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? 'http://localhost:3000';6 const [emailsResp, formsResp] = await Promise.all([7 fetch(`${baseUrl}/api/hubspot/emails`, { next: { revalidate: 300 } }),8 fetch(`${baseUrl}/api/hubspot/forms`, { next: { revalidate: 300 } }),9 ]);10 const [emailsData, formsData] = await Promise.all([11 emailsResp.json(),12 formsResp.json(),13 ]);14 return { emails: emailsData.emails ?? [], forms: formsData.forms ?? [] };15}1617export default async function MarketingDashboard() {18 const { emails, forms } = await getMarketingData();1920 const totalLeads = forms.reduce(21 (sum: number, f: { totalSubmissions: number }) => sum + f.totalSubmissions,22 023 );24 const avgOpenRate =25 emails.length > 026 ? (emails.reduce((sum: number, e: { openRate: number }) => sum + e.openRate, 0) /27 emails.length).toFixed(1)28 : '0';29 const bestCampaign = emails.sort(30 (a: { openRate: number }, b: { openRate: number }) => b.openRate - a.openRate31 )[0];3233 return (34 <main className="p-6 max-w-7xl mx-auto">35 <h1 className="text-2xl font-bold mb-6">Marketing Performance</h1>36 <div className="grid grid-cols-3 gap-4 mb-8">37 <div className="bg-white rounded-lg p-4 shadow">38 <p className="text-sm text-gray-500">Total Leads</p>39 <p className="text-3xl font-bold">{totalLeads.toLocaleString()}</p>40 </div>41 <div className="bg-white rounded-lg p-4 shadow">42 <p className="text-sm text-gray-500">Avg Email Open Rate</p>43 <p className="text-3xl font-bold">{avgOpenRate}%</p>44 </div>45 <div className="bg-white rounded-lg p-4 shadow">46 <p className="text-sm text-gray-500">Best Campaign</p>47 <p className="text-lg font-semibold truncate">{bestCampaign?.name ?? 'N/A'}</p>48 <p className="text-sm text-green-600">{bestCampaign?.openRate}% open rate</p>49 </div>50 </div>51 </main>52 );53}Pro tip: Use Next.js ISR (revalidate: 300) for the marketing dashboard rather than fetching fresh data on every request — HubSpot analytics data updates with some delay anyway, and a 5-minute cache dramatically reduces API usage.
Expected result: The marketing dashboard page loads with summary metrics, an email campaigns chart, and a forms analytics table — all populated with live data from your HubSpot Marketing Hub account.
Common use cases
Marketing performance dashboard with campaign ROI
Build a weekly marketing performance dashboard that pulls email campaign statistics, form conversion rates, and page view data from HubSpot into a single view. Marketing managers can see open rates, click-through rates, form submissions, and traffic trends without logging into HubSpot — the data is surfaced directly in a custom branded interface.
Build a marketing dashboard page in Next.js that fetches HubSpot email campaign stats from /api/hubspot/emails/stats and form submissions from /api/hubspot/forms/submissions. Display campaigns in a table with sent count, open rate, click rate, and unsubscribe rate. Show a summary card with total leads generated this month from all forms.
Copy this prompt to try it in Bolt.new
Form conversion rate tracker by landing page
Track which landing pages and forms are converting visitors to leads most effectively. For each HubSpot form, show the total submission count, the conversion rate (submissions divided by page views for the page the form is embedded on), and the top five fields submitted most frequently. Use this data to identify which lead magnets are performing best.
Create a forms analytics page that fetches all HubSpot forms from /api/hubspot/forms and displays them with submission counts, last submission date, and the page URL they are embedded on. Sort by total submissions descending. Add a 'View Submissions' button that opens a slide-over panel with the last 10 submissions for each form.
Copy this prompt to try it in Bolt.new
Email campaign A/B test results viewer
After running A/B test email campaigns in HubSpot, surface the test results in a Bolt dashboard that shows variant A and B side by side with their open rates, click rates, and conversion rates. Include a statistical significance indicator so the marketing team can quickly decide which variant wins without manually calculating p-values.
Build an email A/B test results component that fetches campaign statistics for two HubSpot email campaign IDs and displays them side-by-side. Show open rate, click rate, and unsubscribe rate for each variant with a visual bar comparison. Highlight the winning variant in green.
Copy this prompt to try it in Bolt.new
Troubleshooting
API returns 403 Forbidden with 'MISSING_SCOPES' error when fetching email statistics
Cause: The private app access token was created without the content scope. HubSpot Marketing Hub endpoints (email campaigns, landing pages) require the content scope in addition to the basic CRM scopes.
Solution: Go to HubSpot Settings → Private Apps → your app → Scopes and add the content scope with Read access. After saving, your existing token will automatically gain the new scope — you do not need to rotate the token. Wait a few seconds and retry the API call.
Email statistics API returns empty counters (all zeros) for recently sent campaigns
Cause: HubSpot email statistics have a processing delay of 15–30 minutes after a campaign is sent. Campaigns sent within the last hour will show incomplete statistics.
Solution: Add a data freshness indicator to your dashboard showing the last time the statistics were fetched. For campaigns sent recently, display a 'Statistics are processing — check back in 30 minutes' notice. Use Next.js revalidate with a longer interval (e.g., 300 seconds) for email statistics since they do not need to be real-time.
Forms API returns 401 even though the token works for CRM endpoints like /crm/v3/objects/contacts
Cause: The private app token was created with only CRM scopes (crm.objects.contacts.read, crm.objects.deals.read) but not the forms scope. The HubSpot Forms API at /forms/v2/forms requires a separate forms scope.
Solution: Add the forms scope to your private app in HubSpot Settings → Private Apps → your app → Scopes. Toggle forms to Read access and save. The token automatically includes the new scope without requiring rotation.
Dashboard shows stale data that doesn't update when new campaigns are sent in HubSpot
Cause: The Next.js pages or API routes are using ISR or static caching with a long revalidation period, so they serve cached responses.
Solution: Add a manual refresh button to the dashboard that calls the API routes with cache: 'no-store' to bypass Next.js caching. For the API routes themselves, add Cache-Control: no-store headers when you want real-time data. Alternatively, reduce the revalidate interval to 60 seconds for more frequent updates.
1// Force fresh data in API route2const resp = await hubspotFetch('/marketing/v3/emails?limit=20');3// In the response:4return NextResponse.json(data, {5 headers: { 'Cache-Control': 'no-store' },6});Best practices
- Store HUBSPOT_PRIVATE_TOKEN as a server-side-only environment variable — never prefix it with NEXT_PUBLIC_ or include it in any client component, as it would be exposed in the browser's JavaScript bundle.
- Use ISR (revalidate: 300) for marketing analytics pages — HubSpot statistics update with a processing delay anyway, and caching for 5 minutes prevents unnecessary API calls and keeps your dashboard responsive.
- Add parallel data fetching using Promise.all when the dashboard needs multiple data sources (emails + forms) — this cuts load time roughly in half compared to sequential fetching.
- Respect HubSpot's rate limit of 100 requests per 10 seconds for private apps — when fetching stats for many email campaigns in parallel, batch them in groups of 5 to avoid 429 responses.
- Build the dashboard with a data refresh button and a 'last updated' timestamp — marketing teams appreciate knowing when the data was last fetched, especially during campaign launches.
- Scope private app permissions as narrowly as possible — if you only need to read email statistics and forms, do not grant write access or contact management scopes. Least-privilege access reduces risk if the token is ever leaked.
- Test all analytics endpoints in Bolt's WebContainer preview before deploying — every HubSpot Marketing Hub data endpoint is an outbound HTTPS call that works perfectly in the WebContainer. Only register webhook subscriptions after deployment.
Alternatives
The main HubSpot integration page covers CRM-specific endpoints (contacts, deals, companies) rather than marketing analytics — start there if your primary need is sales pipeline management rather than campaign analytics.
Marketo (Adobe Marketo Engage) provides more advanced marketing automation and lead nurturing workflows, making it a better choice for enterprise teams with complex multi-touch campaign requirements.
Mailchimp is simpler to set up for pure email marketing and has a more generous free tier, making it the better choice if you only need email campaign management without a full CRM.
Google Analytics provides more granular website traffic and user behavior data than HubSpot's Analytics API, and is the right choice when you need detailed funnel analysis and custom event tracking.
Frequently asked questions
Is the HubSpot Marketing Hub API the same as the HubSpot CRM API?
They use the same base URL (https://api.hubapi.com) and the same private app access token, but they are different endpoint namespaces. CRM endpoints live under /crm/v3/objects/ (contacts, deals, companies), while Marketing Hub endpoints use /marketing/v3/emails/ and /forms/v2/. Your private app token works for all endpoints it has scopes for, so you can mix CRM and marketing endpoints in the same integration.
Can I use the same HubSpot private app token I created for CRM integration?
Yes, if that token has the right scopes. Open your existing private app in HubSpot Settings → Private Apps and add the content and forms scopes. The token automatically gains access to the new endpoints without needing to be rotated or replaced. This is the recommended approach — use one private app per integration project rather than creating multiple apps.
Does Bolt.new have a native HubSpot Marketing Hub integration?
No. HubSpot is not one of Bolt's native connectors. You build the integration manually using Next.js API routes and the HubSpot API. Bolt's AI can scaffold the routes and React components when you describe what marketing analytics features you need, but you manage credentials and API calls yourself.
Can I test the marketing analytics dashboard in Bolt's preview without deploying?
Yes, fully. All HubSpot Marketing Hub API calls (fetching email stats, listing forms, reading submissions) are outbound HTTPS requests that work perfectly from Bolt's WebContainer. The only exception is HubSpot webhook subscriptions for real-time events — those require a deployed public URL. For read-only analytics dashboards, you can develop and test entirely in the Bolt preview.
How recent is the email statistics data from the HubSpot API?
HubSpot processes email statistics with a delay of approximately 15–30 minutes after emails are sent or opened. The API returns the most recent processed data, but it will not reflect opens or clicks from the last 30 minutes. For live campaign monitoring during a major send, log into HubSpot directly — the built-in dashboard updates more frequently than the API.
What HubSpot plan do I need to access Marketing Hub analytics through the API?
Basic form submissions and the forms list API work on the free HubSpot plan. Email campaign statistics require Marketing Hub Starter ($18/month) or higher. Advanced analytics like revenue attribution and multi-touch attribution reports require Marketing Hub Professional or Enterprise. The free plan is sufficient for building and testing the integration structure, but you will need a paid plan to see real campaign data.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation