To integrate Replit with HubSpot, store your HubSpot Private App token in Replit Secrets (lock icon π), then use the HubSpot API to create, read, and update CRM contacts, deals, and companies from your server-side Python or Node.js app. Deploy on Autoscale for webhooks or Reserved VM for always-on CRM syncing.
Why Connect Replit to HubSpot?
HubSpot's CRM API gives you programmatic access to the core sales and marketing data that drives your business β contacts, companies, deals, properties, and engagement history. When you connect Replit to HubSpot, you can build custom automations that HubSpot's native workflows can't handle: syncing leads from a custom sign-up form, pushing deal updates from an external system, or triggering personalized emails based on application logic.
The HubSpot CRM API v3 is a modern REST API that uses bearer token authentication through Private Apps β replacing the older API key approach deprecated in 2022. Private Apps let you define exactly which CRM scopes your integration needs, keeping the permission surface minimal. For most use cases, you'll need scopes like crm.objects.contacts.read, crm.objects.contacts.write, and crm.objects.deals.read.
Replit's always-on deployment options make it a practical backend for CRM integrations. You can host a webhook receiver that listens for HubSpot contact updates and triggers downstream actions, or build a scheduled sync job that pulls fresh deal data into your own database. With Replit Secrets handling secure credential storage and the Autoscale deployment type handling traffic spikes, you get a production-ready HubSpot integration without managing infrastructure.
Integration method
Replit connects to HubSpot via the HubSpot CRM REST API using a Private App token stored in Replit Secrets. Your server-side Express or Flask app makes authenticated API calls to read and write CRM data. For real-time updates, you register a HubSpot webhook subscription pointing to your deployed Replit URL.
Prerequisites
- A HubSpot account (free tier is sufficient for the CRM API)
- A Replit account with a Node.js or Python Repl created
- Basic knowledge of REST APIs and JSON
- Node.js (Express) or Python (Flask/requests) installed in your Repl
Step-by-step guide
Create a HubSpot Private App and get your token
Create a HubSpot Private App and get your token
HubSpot Private Apps replaced legacy API keys as the recommended authentication method. To create one, log into HubSpot, click the settings gear icon in the top navigation bar, then go to Integrations > Private Apps in the left sidebar. Click 'Create a private app', give it a name like 'Replit CRM Integration', and navigate to the Scopes tab. For contact management, enable crm.objects.contacts.read and crm.objects.contacts.write. For deal access, add crm.objects.deals.read. You can add more scopes later, but start with the minimum required β this limits blast radius if the token is ever compromised. Click 'Create app', then on the confirmation dialog click 'Continue creating'. HubSpot will display your token starting with 'pat-'. Copy this token immediately β it is only shown once in full. If you lose it, you'll need to rotate it from the Private Apps dashboard. Never paste this token directly into your code files β Replit's Secret Scanner will flag it, and it would end up in version control.
Pro tip: You can rotate your Private App token at any time without deleting the app. Go to Settings > Integrations > Private Apps, click your app, and use the 'Rotate token' option. Update your Replit Secret immediately after rotating.
Expected result: A HubSpot Private App created with CRM read/write scopes and a bearer token copied to your clipboard.
Store your HubSpot token in Replit Secrets
Store your HubSpot token in Replit Secrets
Open your Repl in the Replit editor. Look for the lock icon (π) in the left sidebar β clicking it opens the Secrets pane. Click 'New Secret', enter HUBSPOT_TOKEN as the key, and paste your Private App token as the value. Click 'Add Secret'. The value is now AES-256 encrypted and will be injected as an environment variable when your app runs. Replit Secrets are scoped to the individual Repl β they are never included when someone forks your project, only the key name is visible to others. If you have multiple Repls that need the same token, you can use account-level secrets to share credentials across projects without duplicating them. After adding the secret, you do not need to restart the editor β it takes effect on the next run. For production deployments, secrets sync automatically when you deploy.
1# Python β verify secret is loaded2import os34hubspot_token = os.environ.get("HUBSPOT_TOKEN")5if not hubspot_token:6 raise EnvironmentError("HUBSPOT_TOKEN secret is not set. Add it in Replit Secrets (lock icon).")7print("HubSpot token loaded successfully")Expected result: HUBSPOT_TOKEN appears in the Secrets pane and your test script prints 'HubSpot token loaded successfully' without errors.
Install dependencies and create the HubSpot API client
Install dependencies and create the HubSpot API client
HubSpot provides an official Python SDK (hubspot-api-client) and a Node.js SDK (@hubspot/api-client), but you can also call the REST API directly using requests (Python) or node-fetch (Node.js). For full control and lighter dependencies, the direct HTTP approach is recommended for Replit projects. In Node.js, install Express with npm install express. In Python, Flask and requests are available by default but install them explicitly via the Packages pane or requirements.txt. The HubSpot API base URL is https://api.hubapi.com. All requests require the Authorization: Bearer YOUR_TOKEN header. The CRM objects API v3 endpoint pattern is /crm/v3/objects/{objectType} where objectType is contacts, deals, companies, or other CRM objects. Always use HTTPS and include a Content-Type: application/json header for POST and PATCH requests. Connection timeouts should be set explicitly β HubSpot's API can occasionally be slow during peak times.
1// Node.js β HubSpot API helper (hubspot.js)2const HUBSPOT_TOKEN = process.env.HUBSPOT_TOKEN;3if (!HUBSPOT_TOKEN) throw new Error('HUBSPOT_TOKEN secret not set');45const BASE_URL = 'https://api.hubapi.com';67async function hubspotRequest(method, path, body = null) {8 const options = {9 method,10 headers: {11 'Authorization': `Bearer ${HUBSPOT_TOKEN}`,12 'Content-Type': 'application/json'13 }14 };15 if (body) options.body = JSON.stringify(body);1617 const response = await fetch(`${BASE_URL}${path}`, options);18 if (!response.ok) {19 const error = await response.json();20 throw new Error(`HubSpot API error ${response.status}: ${JSON.stringify(error)}`);21 }22 return response.json();23}2425module.exports = { hubspotRequest };Pro tip: The HubSpot API enforces rate limits: 110 requests per 10 seconds on free accounts, 150 on paid. Add retry logic with exponential backoff for production apps handling high volumes.
Expected result: The hubspot.js helper module exports cleanly with no errors. Dependencies are installed in your Repl.
Create and search CRM contacts
Create and search CRM contacts
With the API helper in place, you can now create contacts, read them by ID, and search for contacts by email or any property. The CRM API v3 uses a consistent pattern: POST to /crm/v3/objects/contacts to create, GET /crm/v3/objects/contacts/{id} to read, PATCH /crm/v3/objects/contacts/{id} to update, and POST /crm/v3/objects/contacts/search to find contacts by property values. When creating a contact, properties are nested under a 'properties' key in the request body. HubSpot contact properties include standard fields like email, firstname, lastname, phone, and company, as well as any custom properties you define in your HubSpot portal. For upsert behavior (create or update based on email), use POST /crm/v3/objects/contacts with the idProperty=email query parameter β HubSpot will match on email and update if found or create if not. The search endpoint accepts filter groups for complex queries and supports pagination with limit and after parameters.
1// Node.js β Create and search contacts (server.js)2const express = require('express');3const { hubspotRequest } = require('./hubspot');4const app = express();5app.use(express.json());67// Create a new contact8app.post('/contacts', async (req, res) => {9 try {10 const { email, firstname, lastname, company } = req.body;11 const contact = await hubspotRequest('POST', '/crm/v3/objects/contacts', {12 properties: { email, firstname, lastname, company }13 });14 res.json({ id: contact.id, properties: contact.properties });15 } catch (err) {16 res.status(500).json({ error: err.message });17 }18});1920// Search contacts by email21app.get('/contacts/search', async (req, res) => {22 try {23 const { email } = req.query;24 const result = await hubspotRequest('POST', '/crm/v3/objects/contacts/search', {25 filterGroups: [{26 filters: [{ propertyName: 'email', operator: 'EQ', value: email }]27 }],28 properties: ['email', 'firstname', 'lastname', 'company', 'hs_lead_status']29 });30 res.json(result.results);31 } catch (err) {32 res.status(500).json({ error: err.message });33 }34});3536app.listen(3000, '0.0.0.0', () => console.log('Server running on port 3000'));Expected result: POST /contacts creates a new HubSpot contact and returns the contact ID. GET /contacts/search?email=test@example.com returns matching contact records.
Receive HubSpot webhook events
Receive HubSpot webhook events
HubSpot can push real-time notifications to your Replit app when CRM events occur β contacts are created, deals change stage, properties are updated, and more. To set this up, you first need a stable deployed URL from Replit. Deploy your app by clicking the Deploy button in the editor and choosing Autoscale deployment. Your app will receive a permanent URL like https://your-app-name.your-username.replit.app. In HubSpot, go to Settings > Integrations > Private Apps, select your app, and click the Webhooks tab. Enter your Replit deployment URL plus the webhook path (e.g., https://your-app.replit.app/webhook) as the Target URL. Add a subscription for the event types you want β for example, contact.creation or deal.propertyChange. HubSpot sends webhook payloads as POST requests with a JSON array of events. Each event includes the objectId, subscriptionType, portalId, changeSource, and eventId fields. You should verify the webhook authenticity by checking the X-HubSpot-Signature header against an HMAC-SHA256 hash of the raw body using your app's client secret.
1// Node.js β HubSpot webhook receiver2const crypto = require('crypto');34const HUBSPOT_CLIENT_SECRET = process.env.HUBSPOT_CLIENT_SECRET;56function verifyHubSpotSignature(rawBody, signature) {7 if (!HUBSPOT_CLIENT_SECRET) return true; // Skip in dev if not set8 const hash = crypto9 .createHmac('sha256', HUBSPOT_CLIENT_SECRET)10 .update(rawBody)11 .digest('hex');12 return hash === signature;13}1415app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {16 const signature = req.headers['x-hubspot-signature'];17 if (!verifyHubSpotSignature(req.body, signature)) {18 return res.status(401).json({ error: 'Invalid signature' });19 }2021 const events = JSON.parse(req.body);22 events.forEach(event => {23 console.log(`Event: ${event.subscriptionType}, Object ID: ${event.objectId}`);24 // Handle each event type25 if (event.subscriptionType === 'contact.creation') {26 console.log(`New contact created: ${event.objectId}`);27 }28 });2930 res.json({ received: true });31});Pro tip: Use Autoscale deployment for webhook receivers β it handles traffic spikes automatically. If you need guaranteed zero-latency reception (e.g., payment confirmations), use Reserved VM instead.
Expected result: HubSpot sends webhook events to your Replit deployment URL and your server logs the event type and object ID for each incoming event.
Python alternative: Flask-based CRM integration
Python alternative: Flask-based CRM integration
If you prefer Python, the pattern is identical but uses the requests library and Flask. Install requests and flask via the Packages pane or add them to requirements.txt. The same environment variable pattern applies β os.environ['HUBSPOT_TOKEN'] retrieves the secret you stored in Replit Secrets. Flask's request object gives you access to POST body data via request.get_json() and query parameters via request.args. For production Python deployments on Replit, Flask works well with Autoscale since it handles concurrent requests via Gunicorn. The HubSpot Python SDK (hubspot-api-client) is also available β install it with pip and it provides typed methods for every CRM operation. However, the direct requests approach has fewer dependencies and is easier to debug when API calls fail, since you can inspect raw request and response objects.
1# Python β Flask HubSpot CRM integration (app.py)2import os3import requests4from flask import Flask, request, jsonify56app = Flask(__name__)78HUBSPOT_TOKEN = os.environ["HUBSPOT_TOKEN"]9BASE_URL = "https://api.hubapi.com"10HEADERS = {11 "Authorization": f"Bearer {HUBSPOT_TOKEN}",12 "Content-Type": "application/json"13}1415@app.route("/contacts", methods=["POST"])16def create_contact():17 data = request.get_json()18 payload = {19 "properties": {20 "email": data.get("email"),21 "firstname": data.get("firstname"),22 "lastname": data.get("lastname"),23 "company": data.get("company")24 }25 }26 resp = requests.post(f"{BASE_URL}/crm/v3/objects/contacts",27 json=payload, headers=HEADERS, timeout=10)28 resp.raise_for_status()29 contact = resp.json()30 return jsonify({"id": contact["id"], "email": contact["properties"]["email"]})3132@app.route("/contacts/<contact_id>", methods=["GET"])33def get_contact(contact_id):34 resp = requests.get(35 f"{BASE_URL}/crm/v3/objects/contacts/{contact_id}",36 params={"properties": "email,firstname,lastname,company"},37 headers=HEADERS, timeout=1038 )39 resp.raise_for_status()40 return jsonify(resp.json()["properties"])4142if __name__ == "__main__":43 app.run(host="0.0.0.0", port=3000)Expected result: Flask app starts successfully, POST /contacts creates HubSpot contacts, and GET /contacts/{id} returns contact properties.
Common use cases
Lead Capture Form to CRM
When visitors submit a form on your website or app, automatically create or update a HubSpot contact with their details plus custom properties. This keeps your CRM current without manual data entry and allows immediate follow-up workflows in HubSpot to fire.
Build an Express server that accepts POST requests from a contact form, validates the email and name fields, then creates or updates a HubSpot contact using the CRM API v3 upsert endpoint. Store the HUBSPOT_TOKEN in Replit Secrets.
Copy this prompt to try it in Replit
Deal Stage Sync from External System
When a deal progresses in your own order management or project tool, push that update to HubSpot so your sales team's pipeline stays accurate. The Replit backend receives events from your internal system and maps them to HubSpot deal stage IDs.
Build a Flask app that listens for POST requests from an internal system, reads deal stage changes, and updates the corresponding HubSpot deal using PATCH /crm/v3/objects/deals/{dealId}. Include error handling and logging.
Copy this prompt to try it in Replit
Webhook-Driven Contact Enrichment
Subscribe to HubSpot's contact creation webhook so every new CRM contact triggers your Replit app. The app can enrich the contact with data from a third-party source β like company size from Clearbit β and write it back as a custom HubSpot property.
Build an Express server that receives HubSpot webhook events for contact creation, fetches additional data about the contact's company from a public API, then updates the HubSpot contact with the enriched properties using the CRM API.
Copy this prompt to try it in Replit
Troubleshooting
401 Unauthorized error when calling HubSpot API
Cause: The Private App token is invalid, expired, or the HUBSPOT_TOKEN secret name doesn't match what your code reads.
Solution: Verify the secret name in Replit Secrets exactly matches the variable name in your code (case-sensitive). Check that the token hasn't been rotated β if you rotated it in HubSpot, update the Replit Secret value. Confirm you're using 'Bearer ' (with a space) before the token in the Authorization header.
1// Check token format2console.log('Token prefix:', process.env.HUBSPOT_TOKEN?.substring(0, 4)); // Should be 'pat-'403 Forbidden β 'MISSING_SCOPES' error in the response body
Cause: Your Private App doesn't have the required CRM scopes enabled for the operation you're trying to perform.
Solution: Go to HubSpot Settings > Integrations > Private Apps, select your app, and click the Scopes tab. Add the missing scope (e.g., crm.objects.contacts.write for creating contacts). After saving, your existing token automatically gains the new permissions β you don't need to generate a new token.
HubSpot webhooks aren't reaching the Replit server
Cause: The webhook URL points to the development Repl URL (which sleeps) rather than the deployed Autoscale URL, or the deployment hasn't started yet.
Solution: Deploy your app using the Deploy button and copy the stable .replit.app URL. Update the webhook Target URL in HubSpot Settings > Integrations > Private Apps > Webhooks. Development URLs (ending in .replit.dev) are only active while the editor is open β always use the deployment URL for webhooks.
CONFLICT error when creating a contact that already exists
Cause: HubSpot returns a 409 Conflict when you try to create a contact with an email that already exists in the CRM.
Solution: Use the upsert endpoint instead of the create endpoint. Add ?idProperty=email to the POST URL β HubSpot will update the existing contact if the email matches, or create a new one if it doesn't exist.
1// Upsert by email (create or update)2const path = '/crm/v3/objects/contacts?idProperty=email';3const result = await hubspotRequest('POST', path, { properties: { email, firstname } });Best practices
- Store HUBSPOT_TOKEN in Replit Secrets (lock icon π) β never hardcode it in source files where it would be committed to version control
- Request only the minimum CRM scopes your integration needs when creating the Private App β this limits damage if the token is compromised
- Use the upsert endpoint (POST with ?idProperty=email) instead of create to avoid duplicate contacts from repeated form submissions
- Implement retry logic with exponential backoff for API calls β HubSpot enforces rate limits of 110 requests per 10 seconds on free accounts
- Deploy as Autoscale for webhook receivers and scheduled sync jobs; switch to Reserved VM if you need guaranteed sub-second webhook response times
- Verify webhook signatures using HMAC-SHA256 with your Private App client secret to prevent spoofed webhook payloads
- Request only the properties you need in search and GET calls using the properties query parameter β smaller payloads are faster and cheaper on rate limits
- Log HubSpot API errors with the full response body, not just the HTTP status code β HubSpot error responses include a category and message field that diagnose the problem
Alternatives
Salesforce is better for enterprise CRM workflows with complex org hierarchies, but requires OAuth 2.0 connected app setup and is significantly more complex to integrate.
Pipedrive has a simpler REST API focused on sales pipelines and is a good choice if you only need deal and contact management without HubSpot's marketing features.
Zoho CRM offers a generous free tier with a REST API similar to HubSpot's and integrates well with other Zoho products if you're already in that ecosystem.
Frequently asked questions
How do I connect Replit to HubSpot?
Create a HubSpot Private App in Settings > Integrations > Private Apps, copy the generated token, then store it as HUBSPOT_TOKEN in Replit Secrets (lock icon π in sidebar). Your server-side code reads it via process.env.HUBSPOT_TOKEN (Node.js) or os.environ['HUBSPOT_TOKEN'] (Python) and uses it as a Bearer token in all HubSpot API requests.
Does Replit work with HubSpot for free?
Yes β HubSpot's free CRM tier includes full API access for contacts, companies, and deals. Replit's free tier can run the integration in development mode. For production use with reliable webhook reception, you'll need a Replit deployment (available on paid plans or limited free deployments).
How do I store my HubSpot API key in Replit?
HubSpot no longer uses API keys β they were deprecated in 2022. Use a Private App token instead. Store it in Replit Secrets by clicking the lock icon (π) in the sidebar, adding HUBSPOT_TOKEN as the key name, and pasting your Private App token as the value. It's AES-256 encrypted and injected as an environment variable at runtime.
Can I receive HubSpot webhooks on a Replit app?
Yes, but you must use a deployed Replit URL, not the development editor URL. Deploy your app (click Deploy > Autoscale), copy the .replit.app URL, and register it in HubSpot Settings > Integrations > Private Apps > Webhooks. Development URLs sleep when the editor is closed and will miss webhook events.
What is the difference between HubSpot API keys and Private Apps?
HubSpot deprecated legacy API keys in November 2022 and removed them entirely. Private Apps are the replacement β they use scoped bearer tokens that you create with specific CRM permissions. Private App tokens are more secure because you control exactly which data the integration can access, and they can be rotated without changing your app configuration.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation