Skip to main content
RapidDev - Software Development Agency
flutterflow-tutorials

How to Integrate FlutterFlow with HubSpot CRM

Integrate FlutterFlow with HubSpot by building a Cloud Function that proxies API calls to HubSpot CRM v3 using a private app access token. Create contacts via POST /crm/v3/objects/contacts, list deals via GET /crm/v3/objects/deals, and update pipelines via PATCH. Mirror data to Firestore for offline access. Never use the deprecated HubSpot API key — use a Private App token created in HubSpot settings.

What you'll learn

  • How to create a HubSpot Private App and use its access token in a Cloud Function for authenticated API calls
  • How to create HubSpot contacts and deals from FlutterFlow using a lead capture form
  • How to display a HubSpot deals pipeline with stage updating from a mobile FlutterFlow app
  • How to receive HubSpot workflow webhook events in a Cloud Function to keep Firestore data synchronized
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner12 min read40-55 minFlutterFlow Free+ (Firebase Cloud Functions required — Blaze plan)March 2026RapidDev Engineering Team
TL;DR

Integrate FlutterFlow with HubSpot by building a Cloud Function that proxies API calls to HubSpot CRM v3 using a private app access token. Create contacts via POST /crm/v3/objects/contacts, list deals via GET /crm/v3/objects/deals, and update pipelines via PATCH. Mirror data to Firestore for offline access. Never use the deprecated HubSpot API key — use a Private App token created in HubSpot settings.

Build a mobile HubSpot CRM companion in FlutterFlow for lead capture and deal management

Sales and marketing teams need mobile access to HubSpot — capturing leads immediately after a networking event, updating deal stages while in a customer meeting, or checking contact history before a call. FlutterFlow can power all of these use cases. HubSpot's CRM API v3 provides endpoints for contacts, companies, deals, tickets, and activities — all accessible via a Private App access token. This tutorial walks through creating the Private App, building a Cloud Function proxy, creating a lead capture form, building a deals pipeline view, and setting up HubSpot workflow webhooks to keep your Firestore data in sync.

Prerequisites

  • A HubSpot account (HubSpot CRM free tier works for basic contact and deal management)
  • Firebase project with Cloud Functions enabled (Blaze plan)
  • Node.js installed locally for Cloud Function development
  • A FlutterFlow project where you want to add CRM features (lead capture form, deals dashboard, contact list)

Step-by-step guide

1

Create a HubSpot Private App and get the access token

HubSpot deprecated API keys in November 2022 — all new integrations require a Private App access token. To create one: log into HubSpot → click your account name (top-right) → Settings (gear icon) → Integrations → Private Apps → Create private app. Give it a name (e.g., FlutterFlowMobile) and a description. Under Scopes, select the permissions your app needs: crm.objects.contacts.read, crm.objects.contacts.write, crm.objects.deals.read, crm.objects.deals.write, crm.objects.companies.read. Click Create app. HubSpot shows the access token once — copy it immediately. Store it in Firebase Functions config: firebase functions:config:set hubspot.access_token='pat-na1-YOUR_TOKEN'. The access token does not expire (unlike OAuth tokens) and is scoped to only the permissions you selected. If you need to add permissions later, edit the Private App scopes and a new token is generated. Revoke the old token from the Private Apps list when you generate a new one.

Expected result: Private App is created in HubSpot and the access token is stored in Firebase Functions config. The Private App shows the scopes: contacts read/write, deals read/write.

2

Build the HubSpot Cloud Function proxy

Create a Cloud Function named hubspot that accepts POST requests from FlutterFlow with an action parameter. The function uses the HubSpot access token to call HubSpot's CRM API v3 at https://api.hubapi.com. Actions to handle: getContacts (GET /crm/v3/objects/contacts with properties and limit), createContact (POST /crm/v3/objects/contacts), getDeals (GET /crm/v3/objects/deals with pipeline and stage), createDeal (POST /crm/v3/objects/deals), updateDeal (PATCH /crm/v3/objects/deals/{id}), associateContactToDeal (PUT /crm/v3/associations/contact/deal/{contactId}/to/{dealId}/contact_to_deal). After fetching data, write to Firestore for offline access.

functions/index.js
1// functions/index.js — HubSpot CRM proxy
2// Install: cd functions && npm install axios
3const functions = require('firebase-functions');
4const admin = require('firebase-admin');
5const axios = require('axios');
6
7admin.initializeApp();
8
9const HS_BASE = 'https://api.hubapi.com';
10
11function hubspotHeaders() {
12 return {
13 Authorization: `Bearer ${functions.config().hubspot.access_token}`,
14 'Content-Type': 'application/json',
15 };
16}
17
18exports.hubspot = functions.https.onRequest(async (req, res) => {
19 res.set('Access-Control-Allow-Origin', '*');
20 if (req.method === 'OPTIONS') { res.status(204).send(''); return; }
21
22 const { action, contactId, dealId, data, limit = 50 } = req.body;
23 const db = admin.firestore();
24 const headers = hubspotHeaders();
25 let result;
26
27 try {
28 if (action === 'getContacts') {
29 const r = await axios.get(`${HS_BASE}/crm/v3/objects/contacts`, {
30 headers,
31 params: {
32 limit,
33 properties: 'firstname,lastname,email,phone,company,lifecyclestage',
34 archived: false,
35 },
36 });
37 const contacts = r.data.results.map((c) => ({
38 id: c.id,
39 firstName: c.properties.firstname || '',
40 lastName: c.properties.lastname || '',
41 email: c.properties.email || '',
42 phone: c.properties.phone || '',
43 company: c.properties.company || '',
44 lifecycleStage: c.properties.lifecyclestage || 'lead',
45 }));
46 // Cache in Firestore
47 const batch = db.batch();
48 contacts.forEach((contact) => {
49 batch.set(db.collection('hs_contacts').doc(contact.id), {
50 ...contact, cachedAt: admin.firestore.FieldValue.serverTimestamp(),
51 });
52 });
53 await batch.commit();
54 result = contacts;
55
56 } else if (action === 'createContact') {
57 const r = await axios.post(`${HS_BASE}/crm/v3/objects/contacts`, {
58 properties: {
59 firstname: data.firstName,
60 lastname: data.lastName,
61 email: data.email,
62 phone: data.phone,
63 company: data.company,
64 hs_lead_status: 'NEW',
65 lifecyclestage: 'lead',
66 },
67 }, { headers });
68 const created = {
69 id: r.data.id,
70 ...data,
71 lifecycleStage: 'lead',
72 };
73 await db.collection('hs_contacts').doc(r.data.id).set({
74 ...created, cachedAt: admin.firestore.FieldValue.serverTimestamp(),
75 });
76 result = created;
77
78 } else if (action === 'getDeals') {
79 const r = await axios.get(`${HS_BASE}/crm/v3/objects/deals`, {
80 headers,
81 params: {
82 limit,
83 properties: 'dealname,amount,dealstage,pipeline,closedate,hs_deal_stage_probability',
84 archived: false,
85 },
86 });
87 const deals = r.data.results.map((d) => ({
88 id: d.id,
89 name: d.properties.dealname || 'Untitled Deal',
90 amount: parseFloat(d.properties.amount || 0),
91 stage: d.properties.dealstage || 'appointmentscheduled',
92 pipeline: d.properties.pipeline || 'default',
93 closeDate: d.properties.closedate || null,
94 probability: parseFloat(d.properties.hs_deal_stage_probability || 0),
95 }));
96 const batch = db.batch();
97 deals.forEach((deal) => {
98 batch.set(db.collection('hs_deals').doc(deal.id), {
99 ...deal, cachedAt: admin.firestore.FieldValue.serverTimestamp(),
100 });
101 });
102 await batch.commit();
103 result = deals;
104
105 } else if (action === 'updateDeal') {
106 await axios.patch(
107 `${HS_BASE}/crm/v3/objects/deals/${dealId}`,
108 { properties: { dealstage: data.stage, amount: data.amount?.toString() } },
109 { headers }
110 );
111 await db.collection('hs_deals').doc(dealId).update({
112 stage: data.stage, cachedAt: admin.firestore.FieldValue.serverTimestamp(),
113 });
114 result = { success: true };
115 }
116
117 res.json({ success: true, data: result });
118 } catch (err) {
119 const msg = err.response?.data?.message || err.message;
120 console.error('HubSpot CF error:', msg);
121 res.status(500).json({ success: false, error: msg });
122 }
123});

Expected result: Cloud Function is deployed. A POST with {action: 'getContacts'} returns HubSpot contacts and writes them to the hs_contacts Firestore collection.

3

Configure FlutterFlow API Manager and build the lead capture form

In FlutterFlow, go to API Manager → Add API Group → name it HubSpotProxy. Set Base URL to your Cloud Function URL. Method: POST. Header: Content-Type: application/json. Add API Calls: getContacts with body {"action": "getContacts", "limit": 50}, createContact with body {"action": "createContact", "data": {"firstName": "[firstName]", "lastName": "[lastName]", "email": "[email]", "phone": "[phone]", "company": "[company]"}}. Test each call and verify the response. Build the lead capture form: add a page or bottom sheet with TextFields for first name, last name, company, email, and phone. Add a Page State variable for each field. The Submit Button Action Flow: call createContact API with the Page State values → on success show SnackBar 'Contact added to HubSpot' → navigate to the Contacts page or dismiss the sheet. This is the core use case: a salesperson meets someone at an event, opens the app, and creates a HubSpot contact in under 30 seconds.

Expected result: Lead capture form submits to the Cloud Function and creates a real HubSpot contact. The contact appears in HubSpot CRM within seconds.

4

Build the deals pipeline view with stage updating

Create a Deals page in FlutterFlow with a Backend Query reading from hs_deals Firestore collection, ordered by closeDate ascending. Display deals in a ListView showing: deal name (bold), amount (formatted as currency), stage (as a colored badge using Conditional Value), and close date. For stage updating: when the user taps a deal item, navigate to a DealDetail page passing the deal document reference as a parameter. On the DealDetail page, show all deal properties and add a DropDown for the deal stage populated with your HubSpot pipeline stages. The standard HubSpot default pipeline stages are: appointmentscheduled (Appointment Scheduled), qualifiedtobuy (Qualified To Buy), presentationscheduled (Presentation Scheduled), decisionmakerboughtin (Decision Maker Bought-In), contractsent (Contract Sent), closedwon (Closed Won), closedlost (Closed Lost). When the user selects a new stage from the DropDown: call updateDeal API with the deal ID and new stage, show a SnackBar 'Stage updated in HubSpot', and update the local Firestore hs_deals document.

Expected result: Deals page shows all deals with their current stages and amounts. Changing the stage DropDown on DealDetail updates the deal in HubSpot and locally in Firestore.

5

Receive HubSpot workflow webhooks to keep Firestore synced

HubSpot workflows can trigger webhooks when deal or contact properties change — for example, when a deal is marked Closed Won in the HubSpot web interface, trigger a webhook to your Cloud Function to update Firestore. Create a separate Cloud Function named hubspotWebhook to receive these events. Register it in HubSpot: your HubSpot account → Automation → Workflows → create or edit a workflow → Actions → Send a webhook → POST to your Cloud Function URL. The webhook payload includes the object type (contact or deal), the record ID, and the properties that changed. Your Cloud Function handler: verify the webhook signature using the HubSpot client secret (from Private App settings → Auth), extract the record ID, fetch the latest record from HubSpot API, and update the corresponding Firestore document. FlutterFlow's real-time Firestore listener picks up the change within 1-2 seconds — the deals pipeline view updates automatically when a deal moves stages in HubSpot.

Expected result: When a deal stage changes in HubSpot's web interface, the FlutterFlow app updates within 2-3 seconds via the webhook → Firestore → real-time listener chain.

Complete working example

HubSpot CRM Integration Architecture
1HubSpot Private App Setup
2==========================
3Location: HubSpot Settings Integrations Private Apps
4Permissions needed:
5 crm.objects.contacts.read + write
6 crm.objects.deals.read + write
7 crm.objects.companies.read
8Token format: pat-na1-XXXXXXXX (North America)
9Storage: firebase functions:config:set hubspot.access_token='pat-na1-...'
10
11Cloud Function: hubspot (HTTPS onRequest)
12==========================================
13Actions:
14 getContacts GET /crm/v3/objects/contacts
15 properties: firstname,lastname,email,phone,company
16 createContact POST /crm/v3/objects/contacts
17 properties: firstname,lastname,email,phone,company,lifecyclestage
18 getDeals GET /crm/v3/objects/deals
19 properties: dealname,amount,dealstage,pipeline,closedate
20 updateDeal PATCH /crm/v3/objects/deals/{id}
21 properties: dealstage, amount
22
23Cloud Function: hubspotWebhook (HTTPS onRequest)
24 Trigger: HubSpot Automation Workflows Send webhook
25 Actions: update hs_contacts or hs_deals in Firestore
26
27Firestore Cache Schema
28=======================
29hs_contacts/{hubspotId}
30 id, firstName, lastName, email, phone, company
31 lifecycleStage, cachedAt
32
33hs_deals/{hubspotId}
34 id, name, amount, stage, pipeline, closeDate
35 probability, cachedAt
36
37FlutterFlow Pages
38==================
39Contacts
40 Backend Query: hs_contacts (Firestore)
41 ListView ContactCard (name, email, company, lifecycle badge)
42 FAB Lead Capture Bottom Sheet
43 Fields: firstName, lastName, company, email, phone
44 Submit createContact SnackBar
45
46Deals
47 Backend Query: hs_deals (Firestore, order by closeDate)
48 ListView DealCard (name, amount, stage badge, closeDate)
49 Tap DealDetail (param: dealRef)
50
51DealDetail (param: dealRef)
52 Backend Query: hs_deals/{dealId}
53 DropDown (stage) On Change updateDeal API update Firestore
54
55HubSpot Default Pipeline Stages
56=================================
57appointmentscheduled Appointment Scheduled
58qualifiedtobuy Qualified To Buy
59presentationscheduled Presentation Scheduled
60decisionmakerboughtin Decision Maker Bought-In
61contractsent Contract Sent
62closedwon Closed Won
63closedlost Closed Lost

Common mistakes

Why it's a problem: Using a HubSpot API key (the old-style token found in Settings → Integrations → API Key) instead of a Private App access token

How to avoid: Create a Private App in HubSpot → Settings → Integrations → Private Apps. The Private App token starts with 'pat-'. Use it in the Authorization: Bearer header in your Cloud Function. The Private App token does not expire and is scoped to only the permissions you explicitly grant.

Why it's a problem: Calling HubSpot API endpoints directly from FlutterFlow's API Manager without a Cloud Function, putting the access token in the API Group headers

How to avoid: Route all HubSpot API calls through a Cloud Function. Store the access token in Firebase Functions config (firebase functions:config:set) where it is only accessible in the server-side function code. The Cloud Function is the only component that knows the HubSpot token.

Why it's a problem: Not requesting the minimum required scopes when creating the Private App, or requesting all available scopes 'just in case'

How to avoid: Start with exactly the scopes you need: crm.objects.contacts.read + write for contact management, crm.objects.deals.read + write for deal management. Add scopes incrementally as you build new features. Each scope addition regenerates the token — update Firebase Functions config when this happens.

Best practices

  • Create the HubSpot Private App with minimum required scopes — start with contacts and deals only, add more as needed
  • Store the Private App token exclusively in Firebase Functions config — never in FlutterFlow API Manager headers, app code, or Firestore documents
  • Cache HubSpot contacts and deals in Firestore for instant display and offline access — HubSpot API calls add 300-800ms latency
  • Use HubSpot property names exactly as documented — firstname not first_name, dealname not deal_name. Test each field name in HubSpot's API explorer at developers.hubspot.com/get-started
  • Set up HubSpot workflow webhooks for high-frequency changes (deal stage updates, contact lifecycle changes) instead of polling — webhooks are real-time and free
  • Log all HubSpot write operations (createContact, updateDeal) to a Firestore audit_log collection with userId and timestamp for accountability
  • Use HubSpot's API rate limits as a guide: 100 requests per 10 seconds for most operations. With Firestore caching, you stay well below this limit

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I am building a FlutterFlow app that needs to integrate with HubSpot CRM. Write a Firebase Cloud Function in Node.js that uses axios to call HubSpot CRM API v3 with a Private App access token. Handle these actions via POST request: getContacts (GET /crm/v3/objects/contacts with properties firstname,lastname,email,phone,company), createContact (POST /crm/v3/objects/contacts), getDeals (GET /crm/v3/objects/deals with properties dealname,amount,dealstage,pipeline,closedate), and updateDeal (PATCH /crm/v3/objects/deals/{id}). After fetching contacts or deals, write them to Firestore hs_contacts and hs_deals collections.

FlutterFlow Prompt

Create a Deals page with a Backend Query on the Firestore hs_deals collection ordered by closeDate. Add a ListView showing each deal's name, amount, and stage. Each stage should show as a colored badge using Conditional Value: closedwon is green, closedlost is red, contractsent is orange, all others are blue. Add a FAB that opens a lead capture bottom sheet with fields for first name, last name, company, email, and phone with a Submit button.

Frequently asked questions

What is the difference between HubSpot and Salesforce integrations in FlutterFlow?

Both require a Cloud Function proxy pattern. The key differences: HubSpot uses a Private App access token (no expiry, no OAuth flow required for simple integrations), while Salesforce requires OAuth 2.0 with token refresh every 2 hours. HubSpot's API is more developer-friendly with consistent v3 REST endpoints at api.hubapi.com. Salesforce's SOQL query language is more powerful for complex filtering. HubSpot has a generous free tier; Salesforce requires Professional or Enterprise for API access. For non-technical founders, HubSpot's integration is significantly simpler.

Can I create HubSpot companies and associate contacts to them?

Yes — add action: 'createCompany' to your Cloud Function with POST /crm/v3/objects/companies (properties: name, domain, phone, city, industry). Then associate the company to a contact with PUT /crm/v3/associations/contact/company/{contactId}/to/{companyId}/contact_to_company. Companies appear in HubSpot's company records associated with the contact. Add getCompanies action to your Cloud Function with the same pattern as getContacts.

How do I get notifications when someone submits a HubSpot form?

HubSpot form submissions can trigger a workflow webhook. Create a HubSpot workflow: Enrollment trigger → 'Contact submits a form' → select the form → Action: Send a webhook → your Cloud Function URL. The webhook payload includes all form field values and the contact's ID. Your Cloud Function can create a Firestore notification document, send a push notification via Firebase Cloud Messaging, or update a leads dashboard. This is useful for marketing landing pages that feed into a FlutterFlow sales tool.

Can I sync HubSpot contacts bidirectionally with my FlutterFlow app's users collection?

Yes — implement a two-way sync. When a user signs up in your FlutterFlow app: the registration Action Flow calls createContact via your HubSpot Cloud Function, stores the returned HubSpot contact ID in the Firestore user document. When HubSpot updates the contact (via workflow webhook): the Cloud Function updates the Firestore user document with the new HubSpot lifecycle stage. When the user updates their profile in FlutterFlow: call updateContact via your Cloud Function. Store hubspotContactId in every Firestore user document as the link between systems.

Is HubSpot CRM free tier sufficient for this integration?

HubSpot CRM free tier includes unlimited contacts, deals, and companies with full REST API access via Private Apps. The free tier limitations that might affect a FlutterFlow integration: no custom deal pipelines (only the default pipeline), limited workflow automation (basic triggers), no custom properties beyond the standard ones. HubSpot Starter ($20/user/month) adds deal stage customization, more properties, and email integration. For a basic lead capture and deal tracking app, the free tier is sufficient.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.