AI Voice Agent + Salesforce Integration: Enterprise Developer Guide
A developer guide to integrating AI voice agents with Salesforce — lead push, call activity logging, and managed packages.
Why Salesforce is different
HubSpot is a REST API with sensible defaults. Salesforce is a platform with its own query language (SOQL), its own composite API batching rules, its own OAuth flavors, and dozens of permission settings that will silently block your writes. Getting an AI voice agent into Salesforce cleanly is an enterprise-grade integration task, not a weekend project.
This guide walks through the integration patterns CallSphere uses for enterprise customers — JWT Bearer OAuth, composite API writes, call activity logging, and lead capture.
caller → voice agent
│
│ tool: lookup_lead_by_phone
▼
SOQL query
│
▼
Lead / Contact / Account
│
▼
Task (type=Call) inserted via composite API
Architecture overview
┌────────────────────┐
│ Voice agent edge │
└─────────┬──────────┘
│ tool call
▼
┌──────────────────────────┐
│ /salesforce service │
│ • JWT Bearer OAuth │
│ • Composite API batching │
│ • Bulk API 2.0 fallback │
└──────────┬───────────────┘
│
▼
┌──────────────────────────┐
│ Salesforce org │
└──────────────────────────┘
Prerequisites
- A Salesforce org (Enterprise, Performance, or Developer edition).
- A Connected App with JWT Bearer flow enabled and a self-signed certificate.
- The
simple-salesforcePython library orjsforcefor Node. - Familiarity with SOQL and the composite REST API.
Step-by-step walkthrough
1. Authenticate with JWT Bearer flow
Server-to-server. No user interaction. Re-used across calls.
import jwt, time, requests
from simple_salesforce import Salesforce
def get_access_token():
claim = {
"iss": SF_CLIENT_ID,
"sub": SF_USERNAME,
"aud": "https://login.salesforce.com",
"exp": int(time.time()) + 300,
}
assertion = jwt.encode(claim, SF_PRIVATE_KEY, algorithm="RS256")
resp = requests.post(
"https://login.salesforce.com/services/oauth2/token",
data={
"grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
"assertion": assertion,
},
)
resp.raise_for_status()
body = resp.json()
return body["access_token"], body["instance_url"]
token, instance = get_access_token()
sf = Salesforce(instance_url=instance, session_id=token)
2. Look up the caller
async def find_lead(phone: str):
soql = f"""
SELECT Id, FirstName, LastName, Company, Status
FROM Lead
WHERE Phone = '{phone}' OR MobilePhone = '{phone}'
LIMIT 1
"""
rows = sf.query(soql)["records"]
return rows[0] if rows else None
3. Log the call as a Task
Salesforce's canonical "call activity" object is a Task with Type = 'Call'. Use the composite API to insert the task and update the lead in one round trip.
def log_call(lead_id: str, subject: str, description: str, duration_sec: int):
payload = {
"compositeRequest": [
{
"method": "POST",
"url": "/services/data/v60.0/sobjects/Task",
"referenceId": "newTask",
"body": {
"Subject": subject,
"Description": description,
"Type": "Call",
"Status": "Completed",
"CallDurationInSeconds": duration_sec,
"WhoId": lead_id,
"ActivityDate": "2026-04-08",
},
},
{
"method": "PATCH",
"url": f"/services/data/v60.0/sobjects/Lead/{lead_id}",
"referenceId": "updateLead",
"body": {"Status": "Working - Contacted"},
},
]
}
return sf.restful("composite", method="POST", json=payload)
4. Create new leads from the call
def create_lead(first: str, last: str, phone: str, company: str, source: str = "AI Voice Agent"):
return sf.Lead.create({
"FirstName": first,
"LastName": last,
"Phone": phone,
"Company": company or "Unknown",
"LeadSource": source,
"Status": "New",
})
5. Expose the tools to the agent
const sfTools = [
{ type: "function", name: "find_lead_by_phone", description: "Look up a Salesforce lead by phone", parameters: { type: "object", properties: { phone: { type: "string" } }, required: ["phone"] } },
{ type: "function", name: "create_lead", description: "Create a new Salesforce lead", parameters: { type: "object", properties: { first: { type: "string" }, last: { type: "string" }, phone: { type: "string" }, company: { type: "string" } }, required: ["last", "phone"] } },
{ type: "function", name: "log_call_task", description: "Log a completed call as a Task", parameters: { type: "object", properties: { lead_id: { type: "string" }, subject: { type: "string" }, description: { type: "string" }, duration_sec: { type: "number" } }, required: ["lead_id", "subject"] } },
];
6. Handle errors like an enterprise integrator
Salesforce will return REQUIRED_FIELD_MISSING, INVALID_SESSION_ID, and DUPLICATES_DETECTED. Map each to a clean tool response the LLM can act on.
Production considerations
- API limits: orgs get 15k-100k API calls per 24h depending on edition. Monitor
Sforce-Limit-Info. - Session expiry: JWT tokens last ~30 minutes. Cache them and refresh proactively.
- Duplicate rules: they will block
Lead.create. Handle theDUPLICATES_DETECTEDerror by surfacing the existing record. - Field-level security: the service user needs explicit field permissions, not just object permissions.
- Governor limits on triggers: an insert can fire Apex triggers that fail silently if your payload is too large.
CallSphere's real implementation
CallSphere connects to Salesforce for enterprise sales and real estate customers. The real estate stack runs 10 agents (buyer specialist, seller specialist, rental specialist, tour coordinator, qualification agent, and more) coordinated through the OpenAI Agents SDK, and the sales pod pairs ElevenLabs TTS with 5 GPT-4 specialists for discovery, qualification, demo scheduling, objection handling, and close.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
The voice plane runs on the OpenAI Realtime API with gpt-4o-realtime-preview-2025-06-03, PCM16 at 24kHz, and server VAD. Salesforce writes flow through a dedicated service that batches composite requests, mirrors every write to per-vertical Postgres for auditing, and attaches sentiment and lead score from the GPT-4o-mini post-call pipeline as custom fields on the Task. CallSphere runs 57+ languages with under one second end-to-end response time.
Common pitfalls
- Per-call OAuth: re-authenticating on every call burns your API quota. Cache the token.
- Ignoring duplicate rules: your agent will hallucinate "I added you" while nothing was saved.
- Skipping composite API: individual writes blow through API limits under load.
- Not handling REQUIRED_FIELD_MISSING: required fields vary by org; surface them as tool errors.
- Hardcoding the API version: pin it, but plan to bump every year.
FAQ
Should I use Bulk API or REST?
REST for single-record writes, Bulk API 2.0 for backfills. Voice agents almost always want REST.
Can I use a managed package instead?
Yes, but the ROI is only there if you are selling to many Salesforce customers. For a single deployment, direct API is simpler.
How do I handle Person Accounts?
Check Account.IsPersonAccount. The field layout differs.
What about sandboxes?
Use a separate Connected App pointed at https://test.salesforce.com for sandbox JWT auth.
How do I test without burning API calls?
Use the cometd streaming API + simulator, or a Salesforce DX scratch org.
Next steps
Looking to integrate Salesforce with an AI voice agent in your org? Book a demo, see the technology page, or check pricing.
#CallSphere #Salesforce #CRM #VoiceAI #EnterpriseIntegration #SOQL #AIVoiceAgents
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.