Skip to content
Technical Guides
Technical Guides15 min read0 views

AI Voice Agent + HubSpot CRM Integration: Complete Developer Guide

Build a production integration between an AI voice agent and HubSpot CRM — contact sync, call logging, and deal creation.

The CRM tax on voice agents

Every voice agent you ship will immediately be asked three questions by the business owner: "did it create the contact?", "did it log the call?", and "did it update the deal?" If the answer to any of those is no, the agent is not useful to their operations team, no matter how good the conversation was.

This guide walks through a production HubSpot integration for an AI voice agent, from the initial contact lookup on ring to the deal stage update at hangup.

ring → lookup contact by phone
        │
        ▼
   existing? ── yes ──► attach call to contact
        │
        no
        │
        ▼
   create_contact(name, phone, lifecycle=lead)
        │
        ▼
   log_call(contact_id, recording_url, transcript)
        │
        ▼
   optionally: create_deal(contact_id, amount, stage)

Architecture overview

┌───────────────────┐
│ Voice agent edge  │
└─────────┬─────────┘
          │ tool call
          ▼
┌──────────────────────────┐
│ /hubspot service         │
│ • OAuth / private app    │
│ • retry + idempotency    │
│ • webhook consumer       │
└──────┬────────────┬──────┘
       │            │
       ▼            ▼
   HubSpot API   Postgres mirror

Prerequisites

  • A HubSpot account with a Private App or OAuth app with the Contacts, Engagements, and Deals scopes.
  • The HubSpot Node or Python SDK.
  • A Postgres table to mirror contact/engagement writes for auditing.

Step-by-step walkthrough

1. Look up the contact on ring

from hubspot import HubSpot
from hubspot.crm.contacts import Filter, FilterGroup, PublicObjectSearchRequest

client = HubSpot(access_token=HS_TOKEN)

async def find_contact_by_phone(phone: str):
    search = PublicObjectSearchRequest(
        filter_groups=[FilterGroup(filters=[
            Filter(property_name="phone", operator="EQ", value=phone),
        ])],
        properties=["firstname", "lastname", "lifecyclestage", "email"],
        limit=1,
    )
    resp = client.crm.contacts.search_api.do_search(public_object_search_request=search)
    return resp.results[0] if resp.results else None

2. Create the contact if missing

from hubspot.crm.contacts import SimplePublicObjectInputForCreate

async def create_contact(phone: str, first: str, last: str):
    payload = SimplePublicObjectInputForCreate(properties={
        "phone": phone,
        "firstname": first,
        "lastname": last,
        "lifecyclestage": "lead",
        "hs_lead_status": "NEW",
    })
    return client.crm.contacts.basic_api.create(simple_public_object_input_for_create=payload)

3. Log the call as an engagement

HubSpot represents a logged call as a Call engagement associated with the contact. Attach the transcript and recording URL.

CALL_ENGAGEMENT = {
    "properties": {
        "hs_timestamp": "2026-04-08T15:00:00Z",
        "hs_call_title": "Inbound — AI receptionist",
        "hs_call_body": "Caller asked about Saturday availability.",
        "hs_call_duration": "185000",
        "hs_call_from_number": "+14155551234",
        "hs_call_to_number": "+14155550000",
        "hs_call_recording_url": "https://storage.yourapp.com/rec/abc.wav",
        "hs_call_status": "COMPLETED",
    },
    "associations": [
        {
            "to": {"id": "contact_id_here"},
            "types": [{"associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 194}],
        }
    ],
}

4. Create or update a deal

For sales verticals, create a deal on first call and move it through the pipeline as the conversation progresses.

async def create_deal(contact_id: str, amount: float, dealname: str):
    payload = {
        "properties": {
            "dealname": dealname,
            "amount": str(amount),
            "dealstage": "appointmentscheduled",
            "pipeline": "default",
        },
        "associations": [
            {"to": {"id": contact_id}, "types": [{"associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 3}]},
        ],
    }
    return client.crm.deals.basic_api.create(simple_public_object_input_for_create=payload)

5. Expose tools to the agent

const hubspotTools = [
  { type: "function", name: "log_call", description: "Log an AI call to HubSpot", parameters: { type: "object", properties: { contact_phone: { type: "string" }, summary: { type: "string" }, recording_url: { type: "string" } }, required: ["contact_phone", "summary"] } },
  { type: "function", name: "create_deal", description: "Create a deal for a known contact", parameters: { type: "object", properties: { contact_id: { type: "string" }, dealname: { type: "string" }, amount: { type: "number" } }, required: ["contact_id", "dealname"] } },
];

6. Consume HubSpot webhooks

HubSpot can push deal stage changes back to you. Consume them to keep your local state in sync and trigger follow-up calls.

Production considerations

  • Rate limits: 100 requests per 10 seconds on Private Apps. Retry with jitter.
  • Association type IDs: HubSpot uses numeric IDs for association types. Cache them.
  • Idempotency: HubSpot does not de-dupe contacts by phone automatically. Search first.
  • PII: call recordings may contain PHI; do not store recording URLs in HubSpot if you are under HIPAA.
  • Pipeline mapping: deal stage IDs differ per portal. Fetch and cache them.

CallSphere's real implementation

CallSphere integrates with HubSpot across its sales and real estate verticals. The sales pod uses ElevenLabs TTS with 5 GPT-4 specialists coordinated through the OpenAI Agents SDK, while the real estate stack runs 10 agents including a buyer specialist, seller specialist, rental specialist, and qualification agent. Both push contact creation, call logging, and deal updates into HubSpot through the pattern above, with every write mirrored into per-vertical Postgres for auditing.

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

The voice layer runs on the OpenAI Realtime API (gpt-4o-realtime-preview-2025-06-03) at 24kHz PCM16 with server VAD, and post-call analytics from a GPT-4o-mini pipeline attach sentiment, intent, and lead score to the HubSpot call engagement as custom properties. CallSphere supports 57+ languages and runs under one second end-to-end on live traffic.

Common pitfalls

  • Hardcoding the deal stage: stage IDs differ between portals.
  • Skipping the contact search: you end up with a HubSpot full of duplicates.
  • Logging recordings under HIPAA: HubSpot is not a HIPAA BAA-covered service by default.
  • Ignoring the association type IDs: your engagements will not show up under the contact.
  • Retrying naively: compound rate-limit errors can lock you out.

FAQ

Should I use OAuth or a Private App?

Private App for single-tenant deployments, OAuth for multi-tenant SaaS.

How fast does HubSpot reflect changes?

Writes are usually visible within 1-2 seconds, but search indices can lag 30-60 seconds.

Can I push transcripts into a custom property?

Yes — create a custom property on the Call engagement and set it during create.

How do I handle merged contacts?

Subscribe to the contact.merged webhook and update your mirror table.

Can I trigger HubSpot workflows from a call?

Yes — enrolling a contact in a workflow is a single API call.

Next steps

Want to see an AI voice agent logging calls straight into HubSpot? Book a demo, read the technology page, or see pricing.

#CallSphere #HubSpot #CRM #VoiceAI #Integration #SalesOps #AIVoiceAgents

Share
C

Written by

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.