How to Integrate AI Agents with HubSpot

Complete guide to connecting AI agents with HubSpot CRM. Covers LangChain, CrewAI, and Lindy AI no-code options for automated lead scoring, contact enrichment, deal pipeline monitoring, and marketing automation.

Before You Start

Prerequisites: HubSpot account (Professional or higher), HubSpot Private App API key, Basic understanding of AI agent concepts

Works with: LangChain, CrewAI, Lindy AI

HubSpot is one of the most widely adopted CRM and marketing platforms — and an exceptionally high-leverage integration for AI agents. An agent with HubSpot access can score leads the moment they come in, enrich contact records with external data, monitor deal pipelines for risk signals, and trigger marketing automation sequences without any human in the loop.

This guide covers every layer of the integration: what your agent can do, how to set up authentication correctly, working Python code for LangChain and CrewAI, and a no-code path using Lindy AI.

What AI Agents Can Do With HubSpot Access#

Before writing a line of code, it helps to map out the full surface area of what becomes possible once an agent has HubSpot API access.

Contact and lead management

  • Create new contacts from inbound sources (web forms, webhook payloads, email parsing)
  • Enrich existing contact records with company data, LinkedIn information, or intent signals
  • Score leads against your ICP criteria and write scores back to custom properties
  • Merge duplicate contacts and flag data quality issues

Deal pipeline operations

  • Read open deals and surface risk indicators (stalled stages, missing close dates, inactivity)
  • Update deal stages based on external signals (contract signed, payment received)
  • Create new deals when a qualified lead triggers a threshold
  • Generate deal summaries and push them to Slack or email

Email and engagement

  • Read contact email history to understand engagement context
  • Trigger marketing email sequences when contacts meet criteria
  • Log outbound activity (calls, meetings) back to the contact timeline
  • Parse inbound emails and route to the correct deal or contact

Workflow triggers

  • Listen for HubSpot webhooks and execute agent logic in response to CRM events
  • Subscribe to contact property changes and react in real time
  • Trigger sequences when deal stages change or contacts reach lifecycle stage thresholds

This is a meaningful step up in automation capability compared to static workflows. For a deeper look at what agents can execute generally, see our guide to AI agent examples in business.


Setting Up HubSpot Authentication: Private App API Key#

HubSpot deprecated its legacy API key in 2022. All integrations now require either a Private App (recommended for server-side agent use) or OAuth (for multi-tenant apps).

For most AI agent use cases, a Private App is the right choice.

Step 1: Create the Private App#

  1. In your HubSpot account, go to SettingsIntegrationsPrivate Apps
  2. Click Create a private app
  3. Name it clearly (e.g., "Lead Qualification Agent" or "Pipeline Monitor Agent")
  4. Under Scopes, select only what your agent needs:

| Agent Use Case | Required Scopes | |---|---| | Read contacts | crm.objects.contacts.read | | Create/update contacts | crm.objects.contacts.write | | Read deals | crm.objects.deals.read | | Update deals | crm.objects.deals.write | | Read companies | crm.objects.companies.read | | Send emails via sequences | sales-email-read, crm.objects.contacts.write |

  1. Click Create app and copy the generated access token immediately. Store it in your secrets manager or .env file — it will not be shown again in full.

Step 2: Test the Connection#

curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  "https://api.hubapi.com/crm/v3/objects/contacts?limit=1"

A successful response confirms your token is valid and the scope is working.


Option 1: No-Code Integration with Lindy AI#

Best for: Marketing and sales teams without engineering resources

Lindy AI provides a native HubSpot connector that uses OAuth to authenticate, requiring no code.

Step 1: Connect Lindy to HubSpot#

  1. In your Lindy workspace, click IntegrationsCRMHubSpot
  2. Click Connect HubSpot — you'll be redirected to the HubSpot OAuth authorization flow
  3. Grant Lindy the requested permissions for the objects your agent will access
  4. Name the connection and save it

Step 2: Build the Lead Scoring Agent#

Create a new Lindy agent and give it the following system instructions:

You are a lead qualification agent for [Company Name].

When you receive a new contact notification from HubSpot:
1. Read the contact's properties (company, job title, company size, industry)
2. Search the web for recent news about their company
3. Score the lead 1-10 based on our ICP: [define your ICP criteria]
4. Update the contact in HubSpot:
   - Set the custom property "ICP Score" to your score
   - Add a note to their timeline explaining your scoring rationale
5. If score >= 7, enroll them in the "High Priority Outreach" HubSpot sequence
6. Post a summary to the #new-qualified-leads Slack channel

Step 3: Set the Trigger#

Configure Lindy to trigger when:

  • A new contact is created in HubSpot
  • A contact's lifecycle stage changes to "Marketing Qualified Lead"
  • A contact submits a specific form

Lindy listens to HubSpot webhook events in real time, so your agent fires within seconds of the triggering event.


Option 2: LangChain with Python#

Best for: Engineering teams requiring custom logic and deep integration

Installation#

pip install langchain langchain-openai hubspot-api-client python-dotenv requests

Create a .env file:

HUBSPOT_ACCESS_TOKEN=pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
OPENAI_API_KEY=sk-...

Build HubSpot Tools#

import os
import json
from hubspot import HubSpot
from hubspot.crm.contacts import SimplePublicObjectInputForCreate
from langchain.tools import tool
from dotenv import load_dotenv

load_dotenv()

hs = HubSpot(access_token=os.getenv("HUBSPOT_ACCESS_TOKEN"))


@tool
def search_contact_by_email(email: str) -> str:
    """Search HubSpot for a contact by email address. Returns contact properties if found."""
    from hubspot.crm.contacts import PublicObjectSearchRequest, Filter, FilterGroup

    filter_obj = Filter(property_name="email", operator="EQ", value=email)
    filter_group = FilterGroup(filters=[filter_obj])
    search_request = PublicObjectSearchRequest(
        filter_groups=[filter_group],
        properties=["firstname", "lastname", "company", "jobtitle", "hs_lead_status", "lifecyclestage"]
    )
    results = hs.crm.contacts.search_api.do_search(public_object_search_request=search_request)

    if results.total == 0:
        return f"No contact found with email: {email}"

    props = results.results[0].properties
    return (
        f"Contact found: {props.get('firstname', '')} {props.get('lastname', '')} "
        f"at {props.get('company', 'Unknown Company')}, "
        f"Title: {props.get('jobtitle', 'Unknown')}, "
        f"Lifecycle Stage: {props.get('lifecyclestage', 'Unknown')}"
    )


@tool
def update_contact_properties(contact_id: str, properties: str) -> str:
    """
    Update a HubSpot contact's properties.
    Pass properties as a JSON string, e.g. '{"hs_lead_status": "QUALIFIED", "icp_score": "8"}'.
    """
    props_dict = json.loads(properties)
    from hubspot.crm.contacts import SimplePublicObjectInput

    update_input = SimplePublicObjectInput(properties=props_dict)
    hs.crm.contacts.basic_api.update(contact_id=contact_id, simple_public_object_input=update_input)
    return f"Contact {contact_id} updated with properties: {properties}"


@tool
def create_contact_note(contact_id: str, note_body: str) -> str:
    """Add a note to a HubSpot contact's activity timeline."""
    from hubspot.crm.objects.notes import SimplePublicObjectInputForCreate as NoteInput
    from hubspot.crm.associations import PublicObjectId, PublicAssociation, AssociationSpec

    note_input = NoteInput(
        properties={"hs_note_body": note_body, "hs_timestamp": str(int(__import__("time").time() * 1000))}
    )
    note = hs.crm.objects.notes.basic_api.create(simple_public_object_input_for_create=note_input)

    # Associate the note with the contact
    hs.crm.objects.notes.associations_api.create(
        note_id=note.id,
        to_object_type="contacts",
        to_object_id=contact_id,
        association_type="note_to_contact"
    )
    return f"Note added to contact {contact_id}"


@tool
def get_deals_by_stage(stage_name: str, limit: int = 10) -> str:
    """Retrieve HubSpot deals in a specific pipeline stage."""
    from hubspot.crm.deals import PublicObjectSearchRequest, Filter, FilterGroup

    filter_obj = Filter(property_name="dealstage", operator="EQ", value=stage_name)
    filter_group = FilterGroup(filters=[filter_obj])
    search_request = PublicObjectSearchRequest(
        filter_groups=[filter_group],
        properties=["dealname", "amount", "closedate", "hubspot_owner_id"],
        limit=limit
    )
    results = hs.crm.deals.search_api.do_search(public_object_search_request=search_request)

    if results.total == 0:
        return f"No deals found in stage: {stage_name}"

    deals = []
    for deal in results.results:
        p = deal.properties
        deals.append(
            f"- {p.get('dealname', 'Unnamed')} | Amount: ${p.get('amount', '0')} | Close: {p.get('closedate', 'No date')}"
        )
    return f"Deals in {stage_name} ({results.total} total):\n" + "\n".join(deals)

Build and Run the Lead Scoring Agent#

from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate

llm = ChatOpenAI(model="gpt-4o", temperature=0)
tools = [search_contact_by_email, update_contact_properties, create_contact_note, get_deals_by_stage]

prompt = ChatPromptTemplate.from_messages([
    ("system", """You are a lead qualification agent with access to HubSpot CRM.

When evaluating a new lead:
1. Search HubSpot for the contact by email
2. Assess their fit using these ICP criteria:
   - Company size: 50-500 employees (high fit)
   - Industry: SaaS, FinTech, or E-commerce (high fit)
   - Title: VP, Director, or C-level (high fit)
3. Score 1-10 (10 = perfect ICP fit)
4. Update the contact with property 'icp_score' set to your score as a string
5. Add a timeline note explaining your reasoning
6. Report what you did and your score rationale.

Use real judgment — do not score everyone 7 or above."""),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

result = executor.invoke({
    "input": "New lead received: sarah.chen@growthtech.io, Director of Marketing at GrowthTech (200-person SaaS company)"
})
print(result["output"])

This pattern implements tool calling in AI agents — the mechanism that lets the LLM decide when to invoke each HubSpot API action versus when to reason without a tool call.


Option 3: CrewAI for Multi-Agent HubSpot Workflows#

Best for: Complex pipelines where specialized agents handle distinct stages

from crewai import Agent, Task, Crew
from crewai.tools import tool

# Reuse the HubSpot tools defined above, plus add an enrichment tool
@tool
def enrich_company_data(company_name: str, domain: str) -> str:
    """Enrich company data using Clearbit or similar enrichment API."""
    # Integrate with your preferred enrichment API
    return f"Company {company_name}: 200 employees, Series B, SaaS vertical, $5M ARR estimate"

# Agent 1: Researcher collects context
lead_researcher = Agent(
    role="Lead Research Specialist",
    goal="Gather comprehensive context about incoming leads",
    backstory="Expert at rapidly profiling companies and contacts from CRM data and public sources",
    tools=[search_contact_by_email, enrich_company_data],
    llm="gpt-4o"
)

# Agent 2: Qualifier scores and updates HubSpot
lead_qualifier = Agent(
    role="Lead Qualification Analyst",
    goal="Score leads against ICP and update HubSpot with findings",
    backstory="Specialist in revenue operations who applies rigorous ICP scoring criteria",
    tools=[update_contact_properties, create_contact_note],
    llm="gpt-4o"
)

research_task = Task(
    description="Research this lead: {lead_info}. Check HubSpot for existing records and gather company context.",
    agent=lead_researcher,
    expected_output="Complete profile including HubSpot status, company size, industry, and recent signals"
)

qualification_task = Task(
    description="Using the research, score this lead 1-10 against ICP criteria. Update HubSpot with the score and detailed reasoning note.",
    agent=lead_qualifier,
    expected_output="ICP score with rationale, confirmation of HubSpot updates"
)

crew = Crew(
    agents=[lead_researcher, lead_qualifier],
    tasks=[research_task, qualification_task],
    verbose=True
)

result = crew.kickoff(inputs={"lead_info": "sarah.chen@growthtech.io, Director of Marketing"})

For a full walkthrough of multi-agent patterns like this, see our CrewAI tutorial.


Use Case: Deal Pipeline Monitoring and Alerts#

Beyond lead scoring, one of the highest-ROI uses for a HubSpot agent is automated pipeline health monitoring. Set up a scheduled agent (cron job or workflow trigger) that runs every morning:

from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate

pipeline_prompt = ChatPromptTemplate.from_messages([
    ("system", """You are a pipeline health monitor for a sales team.

Every morning, analyze the deal pipeline by:
1. Fetching deals in the 'Proposal Sent' stage — flag any with close dates within 7 days
2. Fetching deals in 'Negotiation' stage with no activity in the last 14 days
3. Summarize risk deals in a concise report with deal name, amount, owner, and recommended action
4. Format the output as a briefing ready to share with the sales team"""),
    ("human", "Run the morning pipeline health check for {date}"),
    ("placeholder", "{agent_scratchpad}"),
])

pipeline_agent = create_tool_calling_agent(llm, [get_deals_by_stage], pipeline_prompt)
pipeline_executor = AgentExecutor(agent=pipeline_agent, tools=[get_deals_by_stage], verbose=False)

from datetime import date
report = pipeline_executor.invoke({"date": str(date.today())})
print(report["output"])

Pair this with a Slack notification tool (see our HubSpot + Slack combined workflow examples) to push the daily briefing to your #sales-ops channel automatically.


Security: Minimum Permission Scopes#

Never give your agent more HubSpot access than it needs. The table below maps common agent functions to the minimum required scopes:

| Agent Function | Minimum Scopes | |---|---| | Read contacts | crm.objects.contacts.read | | Create/update contacts | crm.objects.contacts.write | | Read deals | crm.objects.deals.read | | Update deals | crm.objects.deals.write | | Read companies | crm.objects.companies.read | | Add timeline notes | crm.objects.contacts.write | | Enroll in sequences | sales-email-read, crm.objects.contacts.write | | Access marketing emails | content | | Read forms | forms |

Store your access token in a secrets manager (AWS Secrets Manager, HashiCorp Vault, or at minimum environment variables). Never hardcode tokens in source files or commit them to version control.


Common Errors and Fixes#

HTTP 401 Unauthorized: Your access token is invalid or expired. HubSpot Private App tokens do not expire but can be revoked. Verify the token in Settings → Private Apps → your app's token tab.

HTTP 403 Forbidden: The token is valid but lacks the required scope for the operation. Return to your Private App settings and add the missing scope, then generate a new token.

HTTP 429 Too Many Requests: You've exceeded the rate limit. Implement exponential backoff:

import time
import requests

def hubspot_request_with_retry(url, headers, max_retries=5):
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)
        if response.status_code == 429:
            wait_time = 2 ** attempt
            print(f"Rate limited. Waiting {wait_time}s before retry {attempt + 1}")
            time.sleep(wait_time)
            continue
        return response
    raise Exception("Max retries exceeded")

Property not found errors: Custom properties (like icp_score) must be created in HubSpot before the API can write to them. Go to Settings → Properties → Create property before running agents that write custom fields.

Contact not found: The agent searched by email but found no match. Handle this gracefully — check whether the contact should be created first or whether the email has a typo.


Next Steps#

Your HubSpot agent is ready to handle the core workflows. Expand from here: