Skip to main content
RapidDev - Software Development Agency
replit-integrationsStandard API Integration

How to Integrate Replit with HubSpot Marketing Hub

To integrate Replit with HubSpot Marketing Hub, generate a private app access token in HubSpot with marketing scopes, store it in Replit Secrets (lock icon πŸ”’), and call the HubSpot Marketing API from Python or Node.js server-side code to manage forms, email campaigns, contacts, and marketing workflows. Use Autoscale deployment for web apps that capture leads or trigger marketing events.

What you'll learn

  • How to create a HubSpot private app and generate an access token with marketing scopes
  • How to store HubSpot credentials securely in Replit Secrets
  • How to create and update contacts and capture form submissions using Python and Node.js
  • How to trigger HubSpot workflow enrollment and send marketing emails from Replit
  • How to receive HubSpot webhook notifications for marketing events in your Replit app
Book a free consultation
4.9Clutch rating ⭐
600+Happy partners
17+Countries served
190+Team members
Intermediate16 min read30 minutesMarketingMarch 2026RapidDev Engineering Team
TL;DR

To integrate Replit with HubSpot Marketing Hub, generate a private app access token in HubSpot with marketing scopes, store it in Replit Secrets (lock icon πŸ”’), and call the HubSpot Marketing API from Python or Node.js server-side code to manage forms, email campaigns, contacts, and marketing workflows. Use Autoscale deployment for web apps that capture leads or trigger marketing events.

Why Connect Replit to HubSpot Marketing Hub?

HubSpot Marketing Hub is a comprehensive marketing automation platform used by over 190,000 businesses for email campaigns, lead nurturing, form capture, and contact management. Connecting your Replit app to HubSpot enables you to sync leads from any source directly into HubSpot's contact database, trigger automated email workflows based on user behavior in your app, and pull campaign performance data into custom reporting dashboards β€” all without manual data export and import workflows.

The most common integration patterns are pushing new user registrations from your Replit app into HubSpot as marketing contacts, enrolling contacts in specific nurture workflows based on their in-app actions, and receiving webhooks when contacts interact with marketing emails (opens, clicks, unsubscribes). The HubSpot API v3 provides a clean, well-documented REST interface with consistent response formats across all marketing objects.

HubSpot's private app authentication model (introduced in 2022) replaces the older API key system and provides more granular permission control. Your private app access token is a long-lived bearer token that only has access to the specific HubSpot scopes you grant. Store it in Replit Secrets (lock icon πŸ”’) and access it with os.environ['HUBSPOT_ACCESS_TOKEN'] in Python or process.env.HUBSPOT_ACCESS_TOKEN in Node.js. Never include it in client-side code or commit it to Git.

Integration method

Standard API Integration

You connect Replit to HubSpot Marketing Hub by creating a private app in your HubSpot account with the required marketing scopes, obtaining a private app access token, and storing it in Replit Secrets. Your server-side Python or Node.js code calls the HubSpot API v3 using the token in an Authorization: Bearer header. The HubSpot API covers contact management, forms, email campaigns, and workflow triggers through a consistent REST interface.

Prerequisites

  • A Replit account with a Python or Node.js project created
  • A HubSpot account (Marketing Hub Starter or higher for full API access)
  • Super Admin or App Marketplace permissions in your HubSpot account to create private apps
  • Basic familiarity with REST APIs and Bearer token authentication
  • Node.js 18+ or Python 3.10+ (both available on Replit by default)

Step-by-step guide

1

Create a HubSpot Private App and Get an Access Token

Log in to your HubSpot account and click the settings gear icon in the top navigation bar. In the left sidebar, navigate to Account Management > Integrations > Private Apps. Click 'Create a private app'. Give your app a descriptive name like 'Replit Marketing Integration'. On the 'Scopes' tab, select the permissions your integration needs. For marketing workflows, select: crm.objects.contacts.read, crm.objects.contacts.write, marketing-email (for email campaign data), automation (for workflow enrollment), and forms (for form submission data). You can always add more scopes later by editing the private app. Click 'Create app'. A dialog will show your access token β€” it is a long string starting with 'pat-'. Copy it immediately. This token does not expire but can be rotated at any time from the Private Apps settings page. Note your HubSpot Portal ID as well. It appears in the upper-right corner of HubSpot next to your account name as a numeric value (e.g., 12345678). You need the Portal ID for some API endpoints and for constructing webhook verification.

Pro tip: Grant only the minimum scopes your integration actually needs. If you only need to create contacts, you do not need the marketing-email scope. Minimal permissions reduce the blast radius if the token is ever compromised.

Expected result: You have a HubSpot private app access token (starting with 'pat-') and your Portal ID copied and ready to store in Replit Secrets.

2

Store HubSpot Credentials in Replit Secrets

Open your Replit project and click the lock icon πŸ”’ in the left sidebar to open the Secrets pane. Add the following secrets: - Key: HUBSPOT_ACCESS_TOKEN β€” Value: your private app access token (starting with 'pat-') - Key: HUBSPOT_PORTAL_ID β€” Value: your numeric HubSpot Portal ID Click 'Add Secret' after each one. Replit encrypts these values at rest and injects them as environment variables at runtime. In Python, read these with os.environ['HUBSPOT_ACCESS_TOKEN'] and os.environ['HUBSPOT_PORTAL_ID']. In Node.js, use process.env.HUBSPOT_ACCESS_TOKEN. If you are also setting up HubSpot webhooks, you will receive a client secret during webhook subscription creation β€” store this as HUBSPOT_CLIENT_SECRET for signature verification. Do not confuse the private app access token with the OAuth app client secret β€” they are different credentials used for different authentication flows. Replit's Secret Scanner will warn you if you accidentally type an access token directly into a source code file, helping prevent accidental credential exposure.

Pro tip: Store workflow IDs, list IDs, and form IDs that your integration uses frequently as additional Secrets (HUBSPOT_ONBOARDING_WORKFLOW_ID, HUBSPOT_NEWSLETTER_LIST_ID). This makes it easy to update target IDs without changing code.

Expected result: Two Secrets (HUBSPOT_ACCESS_TOKEN and HUBSPOT_PORTAL_ID) appear in the Replit Secrets pane with masked values.

3

Manage Contacts and Form Submissions with Python

The HubSpot API v3 uses Bearer token authentication. All requests include the Authorization: Bearer {token} header. The base URL for most CRM operations is https://api.hubapi.com/. Contact operations use the /crm/v3/objects/contacts/ endpoint. Contacts in HubSpot are identified by their email address or internal HubSpot contact ID. The upsert pattern (create or update) is particularly useful: if a contact with the given email already exists, HubSpot updates it; otherwise, it creates a new contact. Use the POST /crm/v3/objects/contacts endpoint for individual contacts or the /crm/v3/objects/contacts/batch/ endpoint for bulk operations. Install the HubSpot Python SDK with pip install hubspot-api-client, or use the requests library directly. The code below uses requests for explicit control over each API call.

hubspot_client.py
1import os
2import requests
3from typing import Optional
4
5ACCESS_TOKEN = os.environ["HUBSPOT_ACCESS_TOKEN"]
6PORTAL_ID = os.environ["HUBSPOT_PORTAL_ID"]
7BASE_URL = "https://api.hubapi.com"
8
9HEADERS = {
10 "Authorization": f"Bearer {ACCESS_TOKEN}",
11 "Content-Type": "application/json"
12}
13
14def get_contact_by_email(email: str) -> Optional[dict]:
15 """Look up a contact by email address."""
16 url = f"{BASE_URL}/crm/v3/objects/contacts/{email}"
17 params = {
18 "idProperty": "email",
19 "properties": "firstname,lastname,email,company,hs_lead_status,lifecyclestage"
20 }
21 response = requests.get(url, headers=HEADERS, params=params)
22 if response.status_code == 404:
23 return None
24 response.raise_for_status()
25 return response.json()
26
27def create_or_update_contact(
28 email: str,
29 first_name: str = "",
30 last_name: str = "",
31 company: str = "",
32 extra_properties: dict = None
33) -> dict:
34 """
35 Upsert a contact in HubSpot.
36 If the email exists, updates properties. If not, creates a new contact.
37 """
38 properties = {
39 "email": email,
40 "firstname": first_name,
41 "lastname": last_name,
42 "company": company,
43 }
44 if extra_properties:
45 properties.update(extra_properties)
46
47 # Try to create first
48 response = requests.post(
49 f"{BASE_URL}/crm/v3/objects/contacts",
50 json={"properties": properties},
51 headers=HEADERS
52 )
53
54 # If contact exists (409 Conflict), update instead
55 if response.status_code == 409:
56 existing = get_contact_by_email(email)
57 if existing:
58 contact_id = existing['id']
59 update_response = requests.patch(
60 f"{BASE_URL}/crm/v3/objects/contacts/{contact_id}",
61 json={"properties": properties},
62 headers=HEADERS
63 )
64 update_response.raise_for_status()
65 return update_response.json()
66
67 response.raise_for_status()
68 return response.json()
69
70def add_contact_to_list(contact_id: str, list_id: str) -> bool:
71 """Add a contact to a HubSpot static list."""
72 url = f"{BASE_URL}/contacts/v1/lists/{list_id}/add"
73 payload = {"vids": [int(contact_id)]}
74 response = requests.post(url, json=payload, headers=HEADERS)
75 response.raise_for_status()
76 return True
77
78def enroll_in_workflow(contact_email: str, workflow_id: str) -> bool:
79 """Enroll a contact in a HubSpot workflow by email."""
80 url = f"{BASE_URL}/automation/v4/enrollment/automations/{workflow_id}/enrollments/contacts"
81 payload = {"email": contact_email}
82 response = requests.post(url, json=payload, headers=HEADERS)
83 response.raise_for_status()
84 return True
85
86def get_marketing_emails(limit: int = 20) -> list:
87 """Fetch marketing email campaigns and their stats."""
88 url = f"{BASE_URL}/marketing/v3/emails"
89 params = {"limit": limit, "orderBy": "-stats.sent"}
90 response = requests.get(url, headers=HEADERS, params=params)
91 response.raise_for_status()
92 return response.json().get('results', [])
93
94# Example usage
95if __name__ == "__main__":
96 # Create a test contact
97 contact = create_or_update_contact(
98 email="test@example.com",
99 first_name="Jane",
100 last_name="Doe",
101 company="Acme Corp",
102 extra_properties={"lifecyclestage": "lead"}
103 )
104 print(f"Contact: {contact['id']} β€” {contact['properties'].get('email')}")
105
106 # Fetch email campaigns
107 emails = get_marketing_emails(5)
108 print(f"\nMarketing emails: {[e.get('name', 'Unnamed') for e in emails]}")

Pro tip: HubSpot's API has different rate limits depending on your plan. Free and Starter plans allow 100 requests per 10 seconds. Professional and Enterprise plans allow higher limits. Implement retry logic with exponential backoff when you receive a 429 Too Many Requests response.

Expected result: Running the script creates a test contact in HubSpot and prints the contact ID alongside a list of recent marketing email campaign names.

4

Build a Node.js Integration for Lead Capture

For Node.js projects, use the @hubspot/api-client npm package (npm install @hubspot/api-client) for a typed SDK experience, or use axios for direct API calls. The Express server below provides endpoints for capturing leads from a web form, enriching contact records with additional properties, and triggering workflow enrollments. The server exposes a POST /leads endpoint designed to be called from your frontend when a user submits a sign-up or contact form. The endpoint creates or updates the HubSpot contact and optionally enrolls them in a welcome workflow. This pattern keeps all HubSpot API interactions server-side so your access token is never exposed to the browser. Install dependencies with npm install express @hubspot/api-client. The HubSpot SDK handles pagination and rate limiting automatically for collection endpoints.

server.js
1const express = require('express');
2const hubspot = require('@hubspot/api-client');
3
4const app = express();
5app.use(express.json());
6
7const ACCESS_TOKEN = process.env.HUBSPOT_ACCESS_TOKEN;
8const WELCOME_WORKFLOW_ID = process.env.HUBSPOT_ONBOARDING_WORKFLOW_ID || '';
9
10// Initialize HubSpot client
11const hubspotClient = new hubspot.Client({ accessToken: ACCESS_TOKEN });
12
13// Create or update a contact (upsert by email)
14app.post('/leads', async (req, res) => {
15 const { email, firstName, lastName, company, phone, source } = req.body;
16 if (!email) return res.status(400).json({ error: 'email is required' });
17
18 try {
19 const properties = {
20 email,
21 firstname: firstName || '',
22 lastname: lastName || '',
23 company: company || '',
24 phone: phone || '',
25 lead_source: source || 'web-form',
26 lifecyclestage: 'lead'
27 };
28
29 let contactId;
30 try {
31 // Try to create
32 const createResponse = await hubspotClient.crm.contacts.basicApi.create({
33 properties
34 });
35 contactId = createResponse.id;
36 } catch (createErr) {
37 if (createErr.code === 409) {
38 // Contact exists β€” update instead
39 const existing = await hubspotClient.crm.contacts.basicApi.getById(
40 email, ['id'], undefined, undefined, false, 'email'
41 );
42 await hubspotClient.crm.contacts.basicApi.update(existing.id, { properties });
43 contactId = existing.id;
44 } else {
45 throw createErr;
46 }
47 }
48
49 // Optionally enroll in onboarding workflow
50 if (WELCOME_WORKFLOW_ID && contactId) {
51 try {
52 const axios = require('axios');
53 await axios.post(
54 `https://api.hubapi.com/automation/v4/enrollment/automations/${WELCOME_WORKFLOW_ID}/enrollments/contacts`,
55 { email },
56 { headers: { Authorization: `Bearer ${ACCESS_TOKEN}`, 'Content-Type': 'application/json' } }
57 );
58 } catch (workflowErr) {
59 console.warn('Workflow enrollment failed:', workflowErr.message);
60 // Non-critical β€” contact was still created
61 }
62 }
63
64 res.status(201).json({ success: true, contactId });
65 } catch (err) {
66 console.error('HubSpot error:', err.message);
67 res.status(500).json({ error: err.message });
68 }
69});
70
71// Get recent contacts with lifecycle stage
72app.get('/contacts', async (req, res) => {
73 try {
74 const response = await hubspotClient.crm.contacts.basicApi.getPage(
75 parseInt(req.query.limit) || 20,
76 undefined,
77 ['email', 'firstname', 'lastname', 'lifecyclestage', 'createdate']
78 );
79 res.json(response.results);
80 } catch (err) {
81 res.status(500).json({ error: err.message });
82 }
83});
84
85// Get marketing email list
86app.get('/email-campaigns', async (req, res) => {
87 try {
88 const axios = require('axios');
89 const response = await axios.get(
90 'https://api.hubapi.com/marketing/v3/emails',
91 {
92 headers: { Authorization: `Bearer ${ACCESS_TOKEN}` },
93 params: { limit: 20 }
94 }
95 );
96 res.json(response.data.results || []);
97 } catch (err) {
98 res.status(500).json({ error: err.message });
99 }
100});
101
102app.listen(3000, '0.0.0.0', () => {
103 console.log('HubSpot Marketing Hub integration server running on port 3000');
104});

Pro tip: The @hubspot/api-client SDK throws errors with a body property containing the HubSpot API error details. Use err.body?.message or err.response?.body?.message for more descriptive error logging rather than just err.message.

Expected result: The server starts on port 3000. A POST to /leads with an email address creates a contact in HubSpot and returns the contact ID.

5

Set Up HubSpot Webhooks and Deploy

HubSpot can send webhook notifications to your Replit app when contacts are created or updated, when deals change stage, or when specific marketing events occur like email opens and clicks. This real-time data flow eliminates the need to poll HubSpot for changes. To set up webhooks, go to your Private App in HubSpot (Settings > Integrations > Private Apps) and click on your app. Select the 'Webhooks' tab. Click 'Create subscription'. Enter your deployed Replit URL plus the webhook path (e.g., https://your-app.replit.app/hubspot/webhook). Select the event type: contact.creation, contact.propertyChange, deal.stageChange, or email.sent are the most useful for marketing integrations. HubSpot signs webhook payloads with a v3 signature using your app client secret. Verify this signature on every incoming request to prevent processing fake events. The signature is in the X-HubSpot-Signature-v3 header. Deploy your Replit app before registering webhooks. Use Autoscale deployment for marketing apps where webhook volume varies. Click 'Deploy' in the Replit toolbar and wait for the deployment to complete before registering the webhook URL.

webhook_handler.py
1from flask import Flask, request, jsonify
2import os
3import hmac
4import hashlib
5import time
6
7app = Flask(__name__)
8
9ACCESS_TOKEN = os.environ["HUBSPOT_ACCESS_TOKEN"]
10CLIENT_SECRET = os.environ.get("HUBSPOT_CLIENT_SECRET", "")
11
12def verify_hubspot_signature(request_body: bytes, timestamp: str, signature: str) -> bool:
13 """
14 Verify HubSpot webhook signature v3.
15 Validates that the request came from HubSpot.
16 """
17 if not CLIENT_SECRET:
18 return True # Skip verification in development
19
20 # Check timestamp is within 5 minutes to prevent replay attacks
21 if abs(time.time() - int(timestamp) / 1000) > 300:
22 return False
23
24 # Build the string to sign: method + URI + body + timestamp
25 # Note: HubSpot uses the full request URI for v3 signatures
26 expected = hmac.new(
27 CLIENT_SECRET.encode(),
28 request_body + timestamp.encode(),
29 hashlib.sha256
30 ).hexdigest()
31 return hmac.compare_digest(expected, signature)
32
33@app.route('/hubspot/webhook', methods=['POST'])
34def hubspot_webhook():
35 # Verify signature
36 signature = request.headers.get('X-HubSpot-Signature-v3', '')
37 timestamp = request.headers.get('X-HubSpot-Request-Timestamp', '0')
38
39 if CLIENT_SECRET and not verify_hubspot_signature(request.data, timestamp, signature):
40 return jsonify({'error': 'Invalid signature'}), 401
41
42 events = request.json
43 if not events:
44 return jsonify({'error': 'No events'}), 400
45
46 for event in events:
47 event_type = event.get('subscriptionType')
48 object_id = event.get('objectId')
49 portal_id = event.get('portalId')
50
51 print(f"HubSpot event: {event_type} for object {object_id} in portal {portal_id}")
52
53 if event_type == 'contact.creation':
54 # New contact created β€” trigger welcome workflow, send to CRM, etc.
55 print(f"New contact: {object_id}")
56 elif event_type == 'contact.propertyChange':
57 property_name = event.get('propertyName')
58 new_value = event.get('propertyValue')
59 print(f"Contact {object_id} property '{property_name}' changed to '{new_value}'")
60
61 return jsonify({'received': True}), 200
62
63if __name__ == '__main__':
64 app.run(host='0.0.0.0', port=3000)

Pro tip: HubSpot sends webhooks as arrays of events β€” your endpoint receives a JSON array, not a single event object. Always iterate over the events array, as HubSpot may batch multiple events in a single request during high-volume periods.

Expected result: After deployment and webhook registration, new HubSpot contact events appear in your Replit console output, and the webhook subscription shows 'Active' status in HubSpot.

Common use cases

Lead Capture and Contact Sync

When a user submits a sign-up form or completes a key action in your Replit app, automatically create or update a HubSpot contact with their details and any relevant properties like plan tier, signup source, or feature usage. The contact appears immediately in HubSpot's contact database and can be enrolled in onboarding email sequences.

Replit Prompt

Build a Flask endpoint that accepts a lead form POST with email, firstName, lastName, and company fields, creates or updates a HubSpot contact using the HUBSPOT_ACCESS_TOKEN from Replit Secrets, and returns a success response with the contact ID.

Copy this prompt to try it in Replit

Workflow Enrollment Based on App Events

Enroll contacts in specific HubSpot marketing workflows when they reach milestones in your Replit app β€” for example, enrolling a user in a trial-expiry nurture sequence when their free trial ends, or triggering a win-back campaign when a user has not logged in for 30 days. The workflow handles the email sequence scheduling within HubSpot.

Replit Prompt

Write a Python function that takes a user's email address and a workflow ID, looks up the contact in HubSpot by email, and enrolls them in the specified workflow using the POST /automation/v4/enrollment/automations/{workflowId}/enrollments/contacts endpoint.

Copy this prompt to try it in Replit

Marketing Email Performance Dashboard

Build a Replit app that pulls HubSpot email campaign statistics β€” open rates, click rates, bounce rates, and unsubscribe rates β€” for all campaigns in the past quarter and displays them in a sortable table. This gives your marketing team a consolidated view without requiring everyone to log into HubSpot directly.

Replit Prompt

Create an Express server with a GET /email-campaigns endpoint that fetches all marketing emails from HubSpot using the Marketing Emails API, retrieves performance statistics for each campaign, and returns a JSON array sorted by open rate for the past 90 days.

Copy this prompt to try it in Replit

Troubleshooting

API returns 401 Unauthorized β€” 'You don't have permission to access this resource'

Cause: The private app access token is missing the required scope for the endpoint being called. For example, calling the Marketing Emails API without the marketing-email scope returns a 403, not 401. A 401 typically means the token itself is invalid or was revoked.

Solution: Check your private app settings in HubSpot (Settings > Integrations > Private Apps) to verify the token is active and has the required scopes. Edit the private app to add missing scopes. After adding scopes, HubSpot may require you to rotate the token β€” generate a new one and update HUBSPOT_ACCESS_TOKEN in Replit Secrets.

typescript
1# Python: verify token validity with a simple test request
2response = requests.get(
3 'https://api.hubapi.com/oauth/v1/access-tokens/' + ACCESS_TOKEN,
4 headers={'Authorization': f'Bearer {ACCESS_TOKEN}'}
5)
6print(response.json()) # Shows token info including scopes and expiry

POST /crm/v3/objects/contacts returns 409 Conflict

Cause: A contact with this email address already exists in HubSpot. The basic create endpoint does not upsert β€” it only creates new records.

Solution: Implement an upsert pattern: catch 409 responses, look up the existing contact by email using the idProperty=email query parameter, and then PATCH the contact with the new properties. Alternatively, use the batch upsert endpoint at POST /crm/v3/objects/contacts/batch/upsert which handles create-or-update automatically.

typescript
1# Python: upsert using batch endpoint
2def upsert_contacts(contacts: list) -> dict:
3 """contacts = [{email, firstname, lastname, ...}, ...]"""
4 inputs = [
5 {"properties": c, "idProperty": "email"}
6 for c in contacts
7 ]
8 response = requests.post(
9 f"{BASE_URL}/crm/v3/objects/contacts/batch/upsert",
10 json={"inputs": inputs},
11 headers=HEADERS
12 )
13 response.raise_for_status()
14 return response.json()

Workflow enrollment returns 400 β€” 'Contact is not eligible for enrollment'

Cause: The contact does not meet the workflow's re-enrollment criteria, is already enrolled in this workflow, or the workflow is paused or not set to allow manual enrollment.

Solution: In HubSpot, edit the workflow and check Settings > Enrollment options. Enable 'Re-enrollment' if you want to enroll existing contacts again. Also verify the workflow is turned on (Active status) in the workflow editor. Check that the contact's current properties match any enrollment criteria the workflow requires.

Webhooks are never received despite the subscription showing Active in HubSpot

Cause: The webhook URL points to a Replit development server that is offline, or the server is returning non-200 responses causing HubSpot to mark the subscription as failing. HubSpot retries failed webhooks but eventually deactivates subscriptions that consistently fail.

Solution: Deploy your Replit app with Autoscale to get a stable URL. Update the webhook subscription URL in HubSpot to point to the deployed URL. Test the endpoint manually with a curl POST to confirm it returns 200. In HubSpot, check the webhook subscription's 'Event log' tab for delivery attempts and error details.

typescript
1# Test your webhook endpoint locally with a sample payload
2# Run this in your Replit Shell to simulate a HubSpot webhook:
3# curl -X POST http://localhost:3000/hubspot/webhook \
4# -H 'Content-Type: application/json' \
5# -d '[{"subscriptionType": "contact.creation", "objectId": 12345}]'

Best practices

  • Store HUBSPOT_ACCESS_TOKEN and HUBSPOT_PORTAL_ID in Replit Secrets (lock icon πŸ”’) β€” never in source code or Git history.
  • Use a private app access token instead of the legacy HubSpot API key, as the legacy API key is deprecated and no longer supported for new integrations.
  • Implement the upsert pattern for contact creation β€” always handle 409 Conflict responses by updating the existing contact rather than failing.
  • Grant only the minimum scopes needed for your integration β€” if you only sync contacts, you do not need marketing-email or automation scopes.
  • Verify HubSpot webhook signatures using your app client secret before processing events to prevent processing forged requests.
  • Rate limits on Starter plans are 100 requests per 10 seconds β€” implement exponential backoff when you receive 429 responses, especially in batch import scenarios.
  • Deploy with Autoscale for web apps that capture leads or serve dashboards; use Reserved VM only if you have a high-frequency webhook processor that cannot tolerate cold-start delay.
  • Use the batch endpoints (/batch/create, /batch/upsert) when importing more than 10 contacts at a time β€” they are significantly more efficient than individual create requests.

Alternatives

Frequently asked questions

How do I store my HubSpot access token in Replit?

Click the lock icon πŸ”’ in the left sidebar of your Replit project to open the Secrets pane. Add HUBSPOT_ACCESS_TOKEN with your private app access token (starting with 'pat-') and HUBSPOT_PORTAL_ID with your numeric Portal ID. Access them in Python with os.environ['HUBSPOT_ACCESS_TOKEN'] and in Node.js with process.env.HUBSPOT_ACCESS_TOKEN.

Does the HubSpot private app token expire?

No. HubSpot private app access tokens do not have an expiry date β€” they remain valid until you manually rotate or revoke them in the private app settings. Unlike OAuth tokens, they do not require a refresh flow. However, if you change the scopes of your private app, HubSpot may require you to generate a new token for the changes to take effect.

Can I use HubSpot Marketing Hub with Replit on the free tier?

HubSpot's free CRM plan includes basic contact management API access. The Marketing Hub features (email campaigns, workflows, forms API) require Marketing Hub Starter ($20/month) or higher. Replit's free tier supports outbound API calls, but you will need Replit Core for always-on deployments required to receive HubSpot webhooks reliably.

How do I enroll a contact in a HubSpot workflow from Replit?

Use the POST /automation/v4/enrollment/automations/{workflowId}/enrollments/contacts endpoint with the contact's email address in the request body. The workflow must be Active and have manual enrollment enabled. Find the workflow ID in HubSpot under Automation > Workflows β€” it appears in the URL as a numeric ID when you edit the workflow.

How do I find my HubSpot Portal ID?

Your HubSpot Portal ID (also called Hub ID) appears in the top-right corner of your HubSpot account next to your account name as a numeric value. You can also find it in the account settings under Account Management > Account Defaults > Hub ID. Store it as HUBSPOT_PORTAL_ID in Replit Secrets.

What is the difference between HubSpot private apps and the legacy API key?

Private apps are HubSpot's current authentication standard, replacing the legacy API key system which was deprecated in 2022 and removed in 2023. Private apps use OAuth-style bearer tokens with granular scope control, so each integration only has access to the HubSpot data it actually needs. Legacy API keys had full account access and are no longer supported for new integrations.

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.