Use Speak AI with HubSpot
Auto-log every call as a HubSpot Meeting engagement, push sentiment and topics to custom properties, and surface AI summaries inside the contact and deal sidebar. Production-ready in a single afternoon with the Engagements API, or zero code via Zapier.
What you can do
Once Speak is wired into HubSpot, every meeting and call becomes a structured engagement on the matching contact and deal, with AI summary, sentiment, and topics ready for HubSpot Lists, Workflows, and reports.
Auto-log every call as a Meeting engagement
Speak’s media.analyzed webhook fires after every recorded call. A small middleware creates a Meeting on the matching contact and deal with summary, sentiment, deep link, and start/end times. Sales reps stop copy-pasting transcripts.
Push AI insights to HubSpot custom properties
Magic Prompt extracts sentiment, intent, competitors, and topics. Values write to custom properties like speak_last_call_sentiment. HubSpot Lists and Workflows segment from real conversation data, not just form-fill metadata.
Surface Speak summaries inside the HubSpot sidebar
A native React UI extension renders on contact and deal records showing the latest 3 Speak calls with sentiment chips, key moments, and an “Ask Speak about this customer” prompt. CS reps stop tab-switching.
Query Speak from Claude or ChatGPT, scoped to HubSpot
Speak’s MCP server lets sales leadership ask “show me HubSpot contacts where the last call sentiment dropped below neutral and the deal is over $50K” in plain English. No SQL, no dashboards, no exports.
Set up in 3 steps
Pick the path that fits your team. Zapier for SMB and operators. Webhook + REST API for production sales pipelines. UI extension for in-record dashboards.
Sign up for Speak AI
Create a free account at app.speakai.co. You get a 7-day trial with full access. Once you are in, go to Settings > API and copy your API key.
Pick your integration path
Use Speak AI’s Zapier app at zapier.com/apps/speak-ai. Trigger on Speak Media Analyzed, action on HubSpot Create Engagement. Five minutes, zero code.
Forward Speak’s media.analyzed webhook to a small middleware. Middleware POSTs to https://api.hubapi.com/crm/v3/objects/meetings with summary in body and the Speak deep link. Production-ready in an afternoon.
For sidebar dashboards. Build a React UI extension using @hubspot/ui-extensions. The serverless function calls Speak’s /v1/media/insight/:mediaId server-side so the API key never reaches the browser.
Connect Claude Desktop with npx @speakai/mcp-server init, or add the remote MCP server to Claude.ai. Tag your Speak media with HubSpot record IDs and your AI assistant can search across the full HubSpot-linked call library.
Generate a HubSpot Private App token
In HubSpot, go to Settings > Integrations > Private Apps > Create. Required scopes: crm.objects.contacts.write, crm.objects.deals.write, crm.objects.meetings.write. Copy the token (starts with pat-na1-) and store as HUBSPOT_PRIVATE_APP_TOKEN in your middleware environment.
Real workflows, real results
Four production patterns Speak customers ship with HubSpot. Pick the one that fits your team and copy the recipe.
Auto-log every call as a HubSpot Meeting engagement
Speak’s media.analyzed webhook fires after every recorded call. A 30-line middleware creates a Meeting engagement on the matching contact and deal with summary, sentiment, deep link, and start/end times. The customer sees a fully-formed meeting on their HubSpot record within 60 seconds of hangup.
// Body Speak posts to your callback URL (verified 2026-05-07)
// Also: ?eventType=media.analyzed&mediaId=<id> appended to the URL.
{
"eventType": "media.analyzed",
"state": "processed",
"mediaId": "14c8dd9c0a89"
}
// To get transcript, sentiment, and speakers, fetch:
// GET https://api.speakai.co/v1/media/insight/<mediaId>
// Header: x-speakai-key: $SPEAK_API_KEY
// Response shape excerpt:
{
"status": "success",
"data": {
"mediaId": "14c8dd9c0a89",
"name": "Acme Corp - Discovery",
"mediaType": "video",
"duration": { "inSecond": 1265, "start": "00:00:01.280", "end": "00:21:00.995" },
"sourceLanguage": "en-US",
"state": "processed",
"tags": ["meeting-assistant"],
"sentiment": [{
"document": {
"Compound": 25.10,
"Negative": 3.22,
"Neutral": 41.66,
"Positive": 55.10
},
"sentences": [...]
}],
"insight": {
"transcript": [...],
"speakers": [...],
"brands": [...]
},
"mediaUrl": "https://...",
"createdAt": "2026-05-06T17:22:51.269Z"
}
}
}
curl -X POST https://api.hubapi.com/crm/v3/objects/meetings
-H "Authorization: Bearer $HUBSPOT_PRIVATE_APP_TOKEN"
-H "Content-Type: application/json"
-d '{
"properties": {
"hs_timestamp": "2026-05-06T11:00:00.000Z",
"hs_meeting_title": "Acme Corp - Discovery",
"hs_meeting_body": "Speak Summary: Acme is evaluating Speak...
",
"hs_internal_meeting_notes": "Sentiment: positive (0.78)",
"hs_meeting_external_url": "https://app.speakai.co/media/med_abc123",
"hs_meeting_start_time": "2026-05-06T11:00:00.000Z",
"hs_meeting_end_time": "2026-05-06T11:30:00.000Z",
"hs_meeting_outcome": "COMPLETED"
},
"associations": [
{"to":{"id":"28333023942"},"types":[{"associationCategory":"HUBSPOT_DEFINED","associationTypeId":200}]},
{"to":{"id":"5910294833"},"types":[{"associationCategory":"HUBSPOT_DEFINED","associationTypeId":212}]}
]
}'
import express from "express";
const app = express();
app.use(express.json());
app.post("/speak/webhook", async (req, res) => {
// Speak fires {eventType, state, mediaId} -- flat, 3 fields.
const { eventType, state, mediaId } = req.body;
if (eventType !== "media.analyzed" || state !== "processed") return res.sendStatus(204);
res.sendStatus(202); // ack fast; queue advances
// 1. Fetch full insight (transcript, sentiment, speakers, brands).
const insightRes = await fetch(
`https://api.speakai.co/v1/media/insight/${mediaId}`,
{ headers: { "x-speakai-key": process.env.SPEAK_API_KEY } }
);
const { data: media } = await insightRes.json();
// 2. Magic Prompt for a one-paragraph CRM-ready summary.
const summary = await runMagicPrompt(mediaId,
"In one paragraph, summarize this call. End with a clear next step.");
// 3. Resolve HubSpot association IDs from your own mediaId -> CRM map.
const { hubspot_contact_id, hubspot_deal_id } = await mappingStore.lookup(mediaId);
// VADER sentiment is an array; document.Compound is in the range -100..100.
const doc = media.sentiment?.[0]?.document || {};
const sentimentLabel = doc.Compound > 10 ? "Positive"
: doc.Compound < -10 ? "Negative" : "Neutral";
const start = new Date(media.createdAt);
const end = new Date(start.getTime() + (media.duration?.inSecond || 0) * 1000);
const associations = [];
if (hubspot_contact_id) associations.push({
to: { id: hubspot_contact_id },
types: [{ associationCategory: "HUBSPOT_DEFINED", associationTypeId: 200 }],
});
if (hubspot_deal_id) associations.push({
to: { id: hubspot_deal_id },
types: [{ associationCategory: "HUBSPOT_DEFINED", associationTypeId: 212 }],
});
await fetch("https://api.hubapi.com/crm/v3/objects/meetings", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.HUBSPOT_PRIVATE_APP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
properties: {
hs_timestamp: start.toISOString(),
hs_meeting_title: media.name,
hs_meeting_body:
`<p><strong>Speak Summary:</strong> ${summary}</p>` +
`<p><a href="https://app.speakai.co/media/${mediaId}">Open in Speak</a></p>`,
hs_internal_meeting_notes:
`Sentiment: ${sentimentLabel} (Compound ${doc.Compound})`,
hs_meeting_external_url: `https://app.speakai.co/media/${mediaId}`,
hs_meeting_start_time: start.toISOString(),
hs_meeting_end_time: end.toISOString(),
hs_meeting_outcome: "COMPLETED",
},
associations,
}),
});
});
// Helper: fire Magic Prompt + poll until completed (~2-3s typical).
async function runMagicPrompt(mediaId, prompt) {
await fetch("https://api.speakai.co/v1/prompt/", {
method: "POST",
headers: {
"x-speakai-key": process.env.SPEAK_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
mediaIds: [mediaId], prompt,
isStream: false, isIndividualPrompt: true,
}),
});
for (let i = 0; i < 20; i++) {
await new Promise(r => setTimeout(r, 1000));
const r = await fetch(
`https://api.speakai.co/v1/prompt/messages?mediaIds=${mediaId}&pageSize=1`,
{ headers: { "x-speakai-key": process.env.SPEAK_API_KEY } }
);
const j = await r.json();
const msg = j?.data?.history?.[0]?.messages?.[0];
if (msg && msg.state === "completed") return msg.answer;
}
return ""; // Magic Prompt timed out -- skip the summary field.
}
app.listen(3000);
import asyncio, os
from datetime import datetime, timedelta
from fastapi import FastAPI, Request
import httpx
app = FastAPI()
HS_TOKEN = os.environ["HUBSPOT_PRIVATE_APP_TOKEN"]
@app.post("/speak/webhook")
async def speak_webhook(req: Request):
# Speak fires {eventType, state, mediaId} -- flat, 3 fields.
body = await req.json()
if body.get("eventType") != "media.analyzed" or body.get("state") != "processed":
return {"ok": True}
media_id = body["mediaId"]
headers = {"x-speakai-key": os.environ["SPEAK_API_KEY"]}
async with httpx.AsyncClient(timeout=30) as c:
# 1. Fetch full insight (transcript, sentiment, speakers).
ir = await c.get(f"https://api.speakai.co/v1/media/insight/{media_id}", headers=headers)
media = ir.json()["data"]
# 2. Magic Prompt for a CRM-ready summary.
summary = await run_magic_prompt(media_id,
"In one paragraph, summarize this call. End with a clear next step.")
# 3. Resolve HubSpot IDs from your own mediaId -> CRM mapping store.
contact_id, deal_id = await mapping_store_lookup(media_id)
doc = (media.get("sentiment") or [{}])[0].get("document", {})
compound = doc.get("Compound", 0)
sentiment_label = "Positive" if compound > 10 else "Negative" if compound < -10 else "Neutral"
start = datetime.fromisoformat(media["createdAt"].replace("Z", "+00:00"))
end = start + timedelta(seconds=(media.get("duration") or {}).get("inSecond", 0))
associations = []
if contact_id:
associations.append({"to": {"id": contact_id},
"types": [{"associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 200}]})
if deal_id:
associations.append({"to": {"id": deal_id},
"types": [{"associationCategory": "HUBSPOT_DEFINED", "associationTypeId": 212}]})
payload = {
"properties": {
"hs_timestamp": start.isoformat(),
"hs_meeting_title": media["name"],
"hs_meeting_body": (
f"<p><strong>Speak Summary:</strong> {summary}</p>"
f"<p><a href='https://app.speakai.co/media/{media_id}'>Open in Speak</a></p>"
),
"hs_internal_meeting_notes": f"Sentiment: {sentiment_label} (Compound {compound})",
"hs_meeting_external_url": f"https://app.speakai.co/media/{media_id}",
"hs_meeting_start_time": start.isoformat(),
"hs_meeting_end_time": end.isoformat(),
"hs_meeting_outcome": "COMPLETED",
},
"associations": associations,
}
async with httpx.AsyncClient() as c:
r = await c.post(
"https://api.hubapi.com/crm/v3/objects/meetings",
headers={"Authorization": f"Bearer {HS_TOKEN}", "Content-Type": "application/json"},
json=payload,
)
r.raise_for_status()
return {"ok": True}
async def run_magic_prompt(media_id: str, prompt: str) -> str:
"""Fire Magic Prompt + poll until completed (~2-3s typical)."""
headers = {"x-speakai-key": os.environ["SPEAK_API_KEY"], "Content-Type": "application/json"}
async with httpx.AsyncClient(timeout=30) as c:
await c.post("https://api.speakai.co/v1/prompt/", headers=headers, json={
"mediaIds": [media_id], "prompt": prompt,
"isStream": False, "isIndividualPrompt": True,
})
for _ in range(20):
await asyncio.sleep(1)
r = await c.get(
"https://api.speakai.co/v1/prompt/messages",
params={"mediaIds": media_id, "pageSize": 1},
headers={"x-speakai-key": os.environ["SPEAK_API_KEY"]},
)
history = r.json().get("data", {}).get("history", [])
msg = history[0]["messages"][0] if history and history[0].get("messages") else None
if msg and msg.get("state") == "completed":
return msg.get("answer", "")
return "" # timed out
Use association type ID 206 instead of 212 if you log to Calls (/crm/v3/objects/calls) instead of Meetings.
Verified against production 2026-05-07. Speak fires a thin notification (eventType, state, mediaId only). Your middleware fetches full insights via GET /v1/media/insight/:mediaId, then posts to the destination CRM.
Push Speak insights to HubSpot custom properties
Magic Prompt extracts sentiment, intent, competitors, and topics from each call. Values write to custom contact and deal properties so HubSpot Lists, Workflows, and reports segment on real conversation data, not just form-fill metadata.
curl -X POST https://api.hubapi.com/crm/v3/properties/contacts
-H "Authorization: Bearer $HUBSPOT_PRIVATE_APP_TOKEN"
-H "Content-Type: application/json"
-d '{
"name": "speak_last_call_sentiment",
"label": "Speak - Last Call Sentiment",
"groupName": "contactinformation",
"type": "number",
"fieldType": "number"
}'
curl -X PATCH
https://api.hubapi.com/crm/v3/objects/contacts/28333023942
-H "Authorization: Bearer $HUBSPOT_PRIVATE_APP_TOKEN"
-H "Content-Type: application/json"
-d '{
"properties": {
"speak_last_call_sentiment": "0.78",
"speak_topics_discussed": "pricing;integrations;security",
"speak_competitors_mentioned": "Gong;Chorus"
}
}'
async function pushInsightsToHubSpot(contactId, sentiment, topics, competitors) {
const r = await fetch(
`https://api.hubapi.com/crm/v3/objects/contacts/${contactId}`,
{
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.HUBSPOT_PRIVATE_APP_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
properties: {
speak_last_call_sentiment: String(sentiment),
speak_topics_discussed: topics.join(";"),
speak_competitors_mentioned: competitors.join(";"),
},
}),
}
);
if (!r.ok) throw new Error(`HubSpot PATCH failed: ${r.status}`);
return r.json();
}
HubSpot enumeration properties use semicolons as separators. Numbers must be passed as strings via the API.
Show Speak summaries in the contact and deal sidebar
A native React UI extension renders on contact and deal records showing the latest Speak calls with summary, sentiment chip, and key moments. The serverless function calls Speak’s REST API server-side so the API key never reaches the browser.
- Set up a HubSpot project with
hs project create, scaffold a UI extension card. - Store your Speak API key as a serverless function secret. Don’t ship it to the React frontend.
- The React component calls
hubspot.serverless('fetchSpeakInsights', { params }). The serverless function fetches Speak’sGET /v1/media/insight/:mediaIdand returns the response. - Deploy with
hs project upload. The card appears on contact and deal record pages.
// src/app/extensions/SpeakSidebar.serverless.js
exports.main = async (context) => {
const { mediaId } = context.parameters;
const resp = await fetch(
`https://api.speakai.co/v1/media/insight/${mediaId}`,
{ headers: { "x-speakai-key": process.env.SPEAK_API_KEY } }
);
const { topics, sentiment, summary, transcript } = await resp.json();
return { topics, sentiment, summary, transcript };
};
// src/app/extensions/SpeakSidebar.jsx
import { hubspot, Tile, Text, Tag } from "@hubspot/ui-extensions";
const SpeakSidebar = ({ context, runServerless }) => {
// call runServerless('fetchSpeakInsights', { params: { mediaId } })
return (
<Tile>
<Text format={{ fontWeight: "bold" }}>Latest Speak call</Text>
<Tag>Sentiment: positive</Tag>
<Text>Summary text from Speak</Text>
</Tile>
);
};
hubspot.extend(({ context, runServerlessFunction }) => (
<SpeakSidebar context={context} runServerless={runServerlessFunction} />
));
UI extensions require HubSpot Sales Hub Pro or above. Free and Starter editions do not support custom UI cards.
Query Speak from Claude or ChatGPT, scoped to HubSpot
Connect Speak’s official MCP server to Claude or ChatGPT and your team queries the entire HubSpot-tagged Speak library through conversation. Tag Speak media with HubSpot record IDs (use case 1 already does this) and Claude can search across the linked call history.
# Claude Desktop / Claude Code (auto-detects your installation)
npx @speakai/mcp-server init
# Paste your Speak API key when prompted. Setup takes about 2 minutes.
# Claude.ai (web) and ChatGPT MCP connector
# Settings > Integrations > Add MCP Server
# Remote URL: https://api.speakai.co/v1/mcp
# Auth header: x-speakai-key: YOUR_SPEAK_API_KEY
# Verify the connection responds:
curl -s https://api.speakai.co/v1/mcp
-H "x-speakai-key: $SPEAK_API_KEY"
-H "Accept: application/json"
2. Example prompts your team can use today:
- “Show me HubSpot contacts where the latest Speak call sentiment was negative and the deal is over $50K.”
- “Pull verbatim quotes from Speak calls tagged with HubSpot deal IDs in the Demo stage. Group by competitor mentioned.”
- “Summarize every Speak call this week for HubSpot contacts owned by Sarah. Flag any that mention budget or timeline objections.”
- “Compare discovery completeness across our top 3 SDRs using Speak calls linked to closed-won HubSpot deals from Q1.”
- “Find the Speak call from last Tuesday tied to HubSpot deal 5910294833 and pull the action items into a follow-up doc.”
Works with Claude.ai, Claude Desktop, Claude Code, and ChatGPT MCP connectors. View MCP server →
Why Speak AI + HubSpot
HubSpot is your customer-of-record. Speak is your conversation-of-record. The combination turns every call into structured data that HubSpot Lists, Workflows, and reports can act on.
Multi-source ingest, not just HubSpot meetings
The same Speak workspace ingests Zoom, Teams, Meet, Webex, Twilio, file uploads, and embedded recorder submissions. Pipe everything into a single HubSpot record so reps see one timeline, not five.
Custom Magic Prompts, not fixed reports
Score discovery completeness, extract competitor mentions, detect MEDDIC stage, surface objection patterns. Save prompts once and they run on every HubSpot-linked Speak call.
MCP-native for Claude and ChatGPT
Speak’s official MCP server exposes 83 tools to AI assistants. Sales leaders query the HubSpot-linked call library in plain English instead of building dashboards or running SQL.
Shareable embeds, not just internal dashboards
Every Speak transcript and analysis can be shared as a public or private embed. Send a HubSpot deal stakeholder a Speak summary without granting them HubSpot access.
Teams trust Speak AI for their most important calls
4.9 on G2
“Speak AI has been instrumental in transforming how we handle qualitative data. The transcription accuracy is impressive, and the NLP insights save us hours of manual analysis.”
Research Director | Consulting Firm
“We switched from Otter.ai and the depth of analysis is on another level. Sentiment scoring, keyword extraction, and theme detection all happen automatically.”
Product Manager | SaaS Company
“The ability to search across all our customer calls and pull specific moments is a game-changer for our sales team.”
Head of Sales | Enterprise Tech
How to use Speak AI with HubSpot for call analysis and CRM intelligence
HubSpot is the system of record for sales, marketing, and customer service teams. Reps log activities. Marketers run campaigns from contact properties. Service teams resolve tickets against contact history. Every one of those motions is sharper when the underlying calls are transcribed, analyzed, and structured. Speak AI is what turns a meeting recording into HubSpot-shaped data.
Where Speak fits in the HubSpot record lifecycle
Speak runs after the call ends. The Speak Meeting Assistant joins Zoom, Teams, Meet, and Webex calls, records the audio, transcribes in 100+ languages, and runs AI analysis. When analysis completes, Speak fires a media.analyzed webhook with the full payload. That webhook is the integration point for HubSpot – your middleware turns it into a Meeting engagement, custom property update, or timeline event.
Engagements API vs Activity Timeline events
Two valid patterns. The Engagements API (/crm/v3/objects/meetings) creates a first-class meeting record that appears in the standard Activities tab and respects HubSpot’s owner, association, and reporting model. Activity Timeline events (/crm/v3/timeline/events) create branded “Speak: Call analyzed” events with custom icons and templates. Use Engagements for sales call logging where the meeting is the object of record. Use Timeline for branded supplementary signals (sentiment alerts, transcript-ready notifications) that sit alongside native engagements.
How do I push call transcripts to HubSpot automatically?
Three production paths, ranked by lift:
- Webhook + REST API. 30-line middleware. Speak fires
media.analyzed, your middleware POSTs to HubSpot’s Meetings endpoint with summary in the body and the Speak deep link. Highest control, lowest cost. - Zapier. Trigger: Speak Media Analyzed. Action: HubSpot Create Engagement. Set up time: 5 minutes. Best for SMB teams without engineering bandwidth.
- HubSpot UI Extension. React card on the contact and deal record showing the latest Speak calls inline. Higher implementation cost, highest agent productivity payoff.
Use cases by role
Sales and RevOps teams use Speak AI with HubSpot to auto-log every meeting and call as a HubSpot Meeting engagement on the matching contact and deal. Magic Prompts score discovery completeness, extract objections, and surface competitor mentions. See Speak AI for sales teams.
Business owners and SMB use the Zapier path to wire Speak into HubSpot in 5 minutes. Every Zoom call ends with a transcript and AI summary attached to the right contact. See Speak AI for business owners.
Sales enablement and training teams turn the HubSpot sidebar into a coaching loop. Speak’s analysis surfaces missed discovery questions, scripted-line drift, and sentiment dips inline on the deal record. See Speak AI for training and development.
Frequently asked questions
Does Speak AI work with HubSpot Free or Starter editions?
The Webhook + REST API path requires a HubSpot Private App, which needs Sales Hub Professional or above ($100/seat/mo). HubSpot Workflow webhooks also require Professional or above. The Zapier path works on Sales Hub Starter through a Zapier-managed integration. The UI extension requires Sales Hub Pro or above.
Do I need to write code, or can I use Zapier?
Zapier covers the Speak Media Analyzed into HubSpot Create Engagement flow with zero code. For production sales pipelines with custom property writes, sidebar UI extensions, or Magic Prompt orchestration, the 30-line Node middleware shown above gives you full control. Both paths use the same Speak workspace.
How do I match a Speak call to the right HubSpot contact?
The cleanest path is to tag Speak media with HubSpot record IDs at upload time. Speak’s Meeting Assistant captures attendee emails on join, which your middleware can resolve to HubSpot contact IDs via the Contacts Search API. Alternative: Calendar integration writes the HubSpot contact ID into the meeting metadata before the call starts.
What HubSpot scopes does the integration need?
Required Private App scopes: crm.objects.contacts.write, crm.objects.deals.write, and the engagement scope you use (crm.objects.meetings.write or crm.objects.calls.write). For custom properties: crm.schemas.contacts.write for one-time setup, crm.objects.contacts.write for value updates.
What languages are supported?
Speak supports transcription in 100+ languages including English, Spanish, French, German, Portuguese, Italian, Dutch, Hebrew, Norwegian, Japanese, Arabic, Hindi, and dozens more. Set the source language with the sourceLanguage field on upload, or let Speak detect it automatically.
Can I try Speak AI for free with my HubSpot account?
Yes. The 7-day trial includes credits for transcription, full API access, and the MCP server. No credit card required. Wire up the recording webhook against a HubSpot sandbox portal and validate the full pipeline before committing.
Start using Speak AI with HubSpot today
83 analysis tools. 100+ languages. Production webhook + REST API. Zero-code Zapier path. MCP-native for Claude and ChatGPT.
Try Speak AI free
Create your account, grab your API key, and wire up the HubSpot Engagements webhook. Full access for 7 days. No credit card required.
View the API docs
Full reference for the upload endpoint, webhook event types, and Magic Prompt API. Plus the Speak Zapier app and the official MCP server on NPM.




