Compare the Conceptual Models of Open CTI and Salesforce Voice

Open CTI is a thin bridge: your CTI adapter runs in an iframe and calls opencti JavaScript APIs to invoke the Salesforce UI. All business logic lives in your adapter. Salesforce Voice with Partner Telephony inverts this: Salesforce owns the rep lifecycle through Omni-Channel, and your connector plays two distinct roles simultaneously.

Your connector calls publishEvent() to push telephony events into Salesforce. When your telephony system fires a “call ringing” event, you translate it into CALL_STARTED; when the call ends, you publish HANGUP. These events drive everything downstream: VoiceCall record creation, AgentWork routing, screen pop, and presence sync.

Connector API methods are what you implement to be notified when telephony actions are initiated from within Salesforce. When a rep clicks Accept in the softphone panel, Salesforce calls your acceptCall(). When they click End Call, Salesforce calls your endCall(). When a supervisor initiates listen-in from Command Center for Service (formerly Omni Supervisor), Salesforce calls your superviseCall().

Return a promise with the result; don’t publish the event yourself. When Salesforce invokes one of your connector methods such as acceptCall(), endCall(), or mute(), return a promise that resolves with the appropriate result object (CallResult, MuteToggleResult). The framework publishes the corresponding event automatically based on your return value.

Do not call publishEvent() yourself from inside these methods. That causes duplicate events and breaks the softphone state machine. Reserve publishEvent() for truly asynchronous backend-initiated events such as an inbound call arriving from the carrier.

In Open CTI, the adapter pushes everything from the iframe. With Salesforce Voice with Partner Telephony, there’s a bidirectional contract via the SDK. This bidirectionality is the core architectural shift. Your connector must implement both directions correctly or the integration is incomplete.

The single biggest data model shift for migration is moving from Task records to VoiceCall records.

In Open CTI, your adapter typically:

  • Pre-creates a Task record at call start (via opencti.saveLog or custom Apex).
  • Updates that Task at wrap-up with duration, disposition, and notes.

The Task is the canonical “call happened” record. Reports run on Activity and Task.

  • Task is generic. It’s used for emails, meetings, and all activities. Voice fields are bolted on (CallDurationInSeconds, CallType, etc.) but the schema isn’t voice-native.
  • The adapter is responsible for record creation. If the adapter crashes mid-call or has a bug, you may have no record.
  • Single record per call. There’s no clean way to model multiparty, consults, or transfers.
  • No native recording link. You need a custom field or a parallel ContentDocument.

For outbound calls, the Salesforce platform creates a VoiceCall record automatically when CALL_STARTED is published. For inbound and transfer calls, the telephony partner creates a VoiceCall record by calling the Create a Voice Call Record Telephony Integration API. VoiceCall is the de facto record for the call going forward, including voice-native fields, multiparty support, recording linkage, and supervisor view.

VoiceCall FieldPopulated ByReplaces in Task Model
CallerIdCALL_STARTED phoneNumberCustom field on Task
CallDurationInSecondsPlatform (computed at HANGUP)Same field on Task, but populated automatically
StatusPlatform (Inbound/Outbound/Missed/Declined)Custom field on Task
StartTime / EndTimePlatformActivity dates
OwnerIdPlatform (assigned rep)Task.OwnerId, but auto-populated from Omni-Channel assignment
RelatedRecordIdYour Omni-Channel flow (Contact/Case routed to)Task.WhatId
CallDispositionConnector or rep ACW inputCustom field
VoiceCallRecordingIdPlatform (if recording enabled)None — required custom field in Open CTI
AdditionalFieldsConnector (callInfo.additionalFields JSON)Custom fields
Multiparty linkage (parentVoiceCallId)Connector at leg creationNone — not modeled in Open CTI
  • Recording is first-class. A linked VoiceCallRecording is automatically created and surfaced on the record page. No custom field, no signed URL configuration.
  • Multiparty is modeled. Each leg gets its own VoiceCall, linked via parentVoiceCallId. Reports across legs are simple SOQL.
  • Supervisor view runs on VoiceCall records. With Open CTI, you had to build your own supervisor dashboard.
  • AI features (transcription, conversation summary, sentiment) attach to VoiceCall natively.
  • Auditability and durability. The platform creates the record, not your code. If your connector crashes, the call is still logged.

Don’t call saveLog. The log already exists as VoiceCall. The question is no longer “how do I log this call?” The question is “what other records do I want to relate to this call?”

Salesforce admins can choose to relate or create any record on top of VoiceCall, not just Tasks. Common customer patterns include:

  • Case: Customer support orgs typically relate the VoiceCall to a Case (new or existing). The VoiceCall ↔ Case relationship surfaces calls on the case timeline.
  • Opportunity: Sales orgs relate VoiceCall to an Opportunity for activity tracking and pipeline reports.
  • Lead: Inbound prospecting relates VoiceCall to a Lead.
  • Account or Contact: VoiceCall already exposes related Contact/Account via RelatedRecordId.
  • Custom object: For industry-specific use cases (Service Order, Patient Visit, Loan Application).
  • Task: Only when the customer specifically wants Task-based reporting (legacy reports, mobile activity feed, etc.). Not the default recommendation.

This is built declaratively as a record-triggered flow on VoiceCall: same pattern, different target object.

Salesforce Voice with Partner Telephony has built-in after conversation work (ACW). When the rep clicks End Wrap-Up, WRAP_UP_ENDED is sent to your connector:

To configure ACW duration as a Salesforce admin, see Salesforce Help: Configure After Conversation Work Time.

This matrix maps every meaningful Open CTI capability to its Salesforce Voice with Partner Telephony equivalent.

Open CTI CapabilitySalesforce Voice ReplacementMigration Action
opencti.screenPop()Omni-Channel Flow → Screen Pop actionEliminate. Rebuild in Flow Builder.
opencti.saveLog()VoiceCall (auto-created) + record-triggered Flow on whichever object the customer choosesEliminate. VoiceCall is the de facto record.
sforce.opencti.searchAndScreenPop()Omni-Channel Flow (Get Records + Screen Pop action) or LWC + Navigation APIMigrate or rebuild in Flow Builder.
Custom presence via runApex() or Presence REST APIsetAgentStatus() connector methodEliminate. Omni-Channel owns presence.
Other runApex() invocationsLWC @wire / imperative Apex + Toolkit APIReplace
Custom status list fetch via ApexOmni-Channel presence status configurations in SetupEliminate
opencti.setSoftphonePanelHeight/Width()Configure in Contact Center setupEliminate
Custom adapter softphone panel (iframe contents)Voice Extension LWC + VoiceExtension FlexiPageMigrate to Voice Extension.
Custom call-lifecycle event listeners in adapterlightning-service-cloud-voice-toolkit-api eventsMigrate to Voice Toolkit.
Custom iframe ↔ page postMessage / DOM eventsLMS via ServiceCloudVoiceMessageChannel__cMigrate to LMS.
opencti.refreshView()notifyRecordUpdateAvailable() from LDS, or Flow that updates the recordMigrate
opencti.enableClickToDial() and disableClickToDial()Automatic. Handled by Salesforce Voice with Partner Telephony.Eliminate
Adapter pushes data to a Visualforce pageLMS channel; Visualforce subscribes via LightningMessageService JS libraryMigrate
opencti.getPhoneContacts() for contact searchCustom LWC with @wire on getRecord or SOSL via ApexMigrate
Custom IVR / routing logicOmni-Channel Flows or Apex Actions in flowsMigrate
External system webhooksTrigger from VoiceCall record-triggered FlowMigrate
Custom analytics queriesVoiceCall SOQL / CRM Analytics Salesforce Voice templateMigrate
Auth / SSO with your backendMove into connector init()Keep
Custom supervisor dashboardCommand Center for Service (native)Eliminate. Command Center for Service uses VoiceCall.
Custom transfer / consult logicaddParticipant(), dial(isConsultCall), conference(), swap()Migrate to multiparty connector.