---
schema_version: "1"
id: d212e34c688ca6e2f39c50f6667b4b51435f9c7a282629de1fe9793e3ac0fae5
title: Compare the Conceptual Models of Open CTI and Salesforce Voice with Partner Telephony
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/service-cloud/api-cti/guides/compare-opencti-voice.md
canonical_url: https://developer.salesforce.com/docs/service/api-cti/guide/compare-opencti-voice.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: 4e81d1562143fc4d221149e61f77fb9776169f13e18d3f080fcfd523cfc671ed
products:
  - Open CTI Developer Guide
  - Service Cloud Developer Content
---

# Compare the Conceptual Models of Open CTI and Salesforce Voice with Partner Telephony

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.

## Role 1: Event Publisher (Connector to Salesforce)

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.

## Role 2: Method Implementation Target (Salesforce to Connector)

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()`.

**important:** 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.

## Data Model Shift: From Task to VoiceCall

The single biggest data model shift for migration is moving from Task records to VoiceCall records.

### Task: The Open CTI Model

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.

#### Limitations

- 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.

### VoiceCall: The Salesforce Voice with Partner Telephony Model

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](https://developer.salesforce.com/docs/atlas.en-us.voice_developer_guide.meta/voice_developer_guide/voice_rest_voicecalls_create.htm) 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 Field                          | Populated By                                    | Replaces in Task Model                                          |
| ---------------------------------------- | ----------------------------------------------- | --------------------------------------------------------------- |
| `CallerId`                               | `CALL_STARTED phoneNumber`                      | Custom field on Task                                            |
| `CallDurationInSeconds`                  | Platform (computed at HANGUP)                   | Same field on Task, but populated automatically                 |
| `Status`                                 | Platform (Inbound/Outbound/Missed/Declined)     | Custom field on Task                                            |
| `StartTime` / `EndTime`                  | Platform                                        | Activity dates                                                  |
| `OwnerId`                                | Platform (assigned rep)                         | `Task.OwnerId`, but auto-populated from Omni-Channel assignment |
| `RelatedRecordId`                        | Your Omni-Channel flow (Contact/Case routed to) | `Task.WhatId`                                                   |
| `CallDisposition`                        | Connector or rep ACW input                      | Custom field                                                    |
| `VoiceCallRecordingId`                   | Platform (if recording enabled)                 | None — required custom field in Open CTI                        |
| `AdditionalFields`                       | Connector (`callInfo.additionalFields` JSON)    | Custom fields                                                   |
| Multiparty linkage (`parentVoiceCallId`) | Connector at leg creation                       | None — not modeled in Open CTI                                  |

#### How VoiceCall Enhances the Experience

- 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.

#### New Behavior for VoiceCall as the De Facto Record

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.

#### After Conversation Work

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:

```javascript
async wrapUpCall(call) {
    await myBackend.endAfterCallWork(call.callId);
    // Return a Promise; framework will publish WRAP_UP_ENDED from your return value.
}
```

To configure ACW duration as a Salesforce admin, see Salesforce Help: [Configure After Conversation Work Time](https://help.salesforce.com/s/articleView?id=service.omnichannel_configure_after_conversation_work_time.htm&type=5).

## Compare Open CTI Capabilities with Salesforce Voice with Partner Telephony Replacements

This matrix maps every meaningful Open CTI capability to its Salesforce Voice with Partner Telephony equivalent.

| Open CTI Capability                                      | Salesforce Voice with Partner Telephony Replacement                                              | Migration Action                                      |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| `opencti.screenPop()`                                    | Omni-Channel Flow → Screen Pop action                                                     | Eliminate. Rebuild in Flow Builder.                   |
| `opencti.saveLog()`                                      | VoiceCall (auto-created) + record-triggered Flow on whichever object the customer chooses | Eliminate. VoiceCall is the de facto record.          |
| `sforce.opencti.searchAndScreenPop()`                    | Omni-Channel Flow (Get Records + Screen Pop action) or LWC + Navigation API               | Migrate or rebuild in Flow Builder.                   |
| Custom presence via `runApex()` or Presence REST API     | `setAgentStatus()` connector method                                                       | Eliminate. Omni-Channel owns presence.                |
| Other `runApex()` invocations                            | LWC `@wire` / imperative Apex + Toolkit API                                               | Replace                                               |
| Custom status list fetch via Apex                        | Omni-Channel presence status configurations in Setup                                      | Eliminate                                             |
| `opencti.setSoftphonePanelHeight/Width()`                | Configure in Contact Center setup                                                         | Eliminate                                             |
| Custom adapter softphone panel (iframe contents)         | Voice Extension LWC + VoiceExtension FlexiPage                                            | Migrate to Voice Extension.                           |
| Custom call-lifecycle event listeners in adapter         | `lightning-service-cloud-voice-toolkit-api` events                                        | Migrate to Voice Toolkit.                             |
| Custom iframe ↔ page postMessage / DOM events            | LMS via `ServiceCloudVoiceMessageChannel__c`                                              | Migrate to LMS.                                       |
| `opencti.refreshView()`                                  | `notifyRecordUpdateAvailable()` from LDS, or Flow that updates the record                 | Migrate                                               |
| `opencti.enableClickToDial()` and `disableClickToDial()` | Automatic. Handled by Salesforce Voice with Partner Telephony.                                   | Eliminate                                             |
| Adapter pushes data to a Visualforce page                | LMS channel; Visualforce subscribes via LightningMessageService JS library                | Migrate                                               |
| `opencti.getPhoneContacts()` for contact search          | Custom LWC with `@wire` on `getRecord` or SOSL via Apex                                   | Migrate                                               |
| Custom IVR / routing logic                               | Omni-Channel Flows or Apex Actions in flows                                               | Migrate                                               |
| External system webhooks                                 | Trigger from VoiceCall record-triggered Flow                                              | Migrate                                               |
| Custom analytics queries                                 | VoiceCall SOQL / CRM Analytics Salesforce Voice with Partner Telephony template                  | Migrate                                               |
| Auth / SSO with your backend                             | Move into connector `init()`                                                              | Keep                                                  |
| Custom supervisor dashboard                              | Command Center for Service (native)                                                       | Eliminate. Command Center for Service uses VoiceCall. |
| Custom transfer / consult logic                          | `addParticipant()`, `dial(isConsultCall)`, `conference()`, `swap()`                       | Migrate to multiparty connector.                      |
