cti-adapter-development

Use when building or debugging a browser-based CTI softphone adapter using the Salesforce Open CTI JavaScript API — covers callcenter.xml definition, Lightning utility item registration, core API methods (enableClickToDial, onClickToDial, screenPop, setSoftphonePanelHeight, saveLog), call logging as Task, and the lightning-click-to-dial LWC component. NOT for Service Cloud Voice Amazon Connect setup, Omni-Channel routing configuration, or CTI adapter AppExchange package selection.

Best use case

cti-adapter-development is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Use when building or debugging a browser-based CTI softphone adapter using the Salesforce Open CTI JavaScript API — covers callcenter.xml definition, Lightning utility item registration, core API methods (enableClickToDial, onClickToDial, screenPop, setSoftphonePanelHeight, saveLog), call logging as Task, and the lightning-click-to-dial LWC component. NOT for Service Cloud Voice Amazon Connect setup, Omni-Channel routing configuration, or CTI adapter AppExchange package selection.

Teams using cti-adapter-development should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/cti-adapter-development/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/apex/cti-adapter-development/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/cti-adapter-development/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How cti-adapter-development Compares

Feature / Agentcti-adapter-developmentStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Use when building or debugging a browser-based CTI softphone adapter using the Salesforce Open CTI JavaScript API — covers callcenter.xml definition, Lightning utility item registration, core API methods (enableClickToDial, onClickToDial, screenPop, setSoftphonePanelHeight, saveLog), call logging as Task, and the lightning-click-to-dial LWC component. NOT for Service Cloud Voice Amazon Connect setup, Omni-Channel routing configuration, or CTI adapter AppExchange package selection.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# CTI Adapter Development

Use this skill when building, diagnosing, or extending a browser-based CTI (Computer Telephony Integration) softphone adapter with the Salesforce Open CTI JavaScript API. It covers the full adapter lifecycle: defining the Call Center in `callcenter.xml`, registering it as a Lightning utility bar item, implementing core Open CTI API calls, enabling click-to-dial on phone fields, logging calls as closed Task records, and integrating with the `lightning-click-to-dial` LWC component.

---

## Before Starting

Gather this context before working on anything in this domain:

- Confirm the org has a Call Center definition registered under **Setup > Call Centers**. Open CTI requires a valid Call Center record tied to the user before any API calls succeed.
- Identify which Lightning App hosts the softphone. The utility bar must be configured for that specific app — Open CTI softphones do NOT appear in the Salesforce mobile app or Classic UI.
- Understand the telephony vendor's hosting requirements: the CTI adapter page (HTML + JS) must be served from a domain added to Salesforce CSP Trusted Sites and CORS allowed-origins. Omitting this causes the `sforce.opencti` namespace to fail silently.

---

## Core Concepts

### Open CTI JavaScript API

Open CTI is a browser-side JavaScript API exposed by Salesforce. The API namespace is `sforce.opencti` and it is injected into the adapter page by the platform — you do not load it via a `<script src>` tag. The adapter page is a hosted HTML file that runs inside the softphone panel iframe rendered in the Lightning utility bar. All API calls are asynchronous; callbacks receive a `response` object with `success`, `returnValue`, and `errors` fields.

Key API methods:

| Method | Purpose |
|---|---|
| `sforce.opencti.enableClickToDial({callback})` | Activates click-to-dial on all phone fields org-wide for the session. Must be called once on adapter load. |
| `sforce.opencti.onClickToDial({listener})` | Registers a callback that fires when the user clicks a phone field link. Callback receives `{number, objectType, recordId, recordName}`. |
| `sforce.opencti.screenPop({type, params, callback})` | Navigates the agent's browser to a Salesforce record or search result. `type` can be `SOBJECT`, `URL`, `FLOW`, or `SEARCH`. |
| `sforce.opencti.setSoftphonePanelHeight({heightPX, callback})` | Resizes the softphone iframe panel at runtime. |
| `sforce.opencti.saveLog({value, callback})` | Creates or updates an Activity (Task) record to log the call. The Task is created in a closed state (`Status: Completed`) by default. |
| `sforce.opencti.getCallObjectReferences({callback})` | Retrieves active call object references for the current session. |
| `sforce.opencti.disableClickToDial({callback})` | Deactivates click-to-dial — use when call is in progress to prevent accidental new calls. |

### callcenter.xml Definition File

Every CTI adapter is defined by a `callcenter.xml` (Call Center Definition File). This XML file describes the adapter to Salesforce and is imported via **Setup > Call Centers > Import**. The root element is `CallCenter` and the key child elements are:

- `InternalName` — unique identifier (no spaces, used in API references)
- `DisplayName` — label shown in Setup UI
- `AdapterUrl` — the fully-qualified HTTPS URL of the adapter HTML page
- `CTIVersion` — must be `"4.0"` for Lightning / Open CTI for Lightning (not `"3.0"` which is Classic-only)
- `RequiresCallCenter` — set to `"true"` for users to be assigned

After import, users must be assigned to the Call Center via **Setup > Call Centers > Manage Call Center Users**.

### Lightning Utility Bar Registration

The softphone panel is added as a utility bar item in a Lightning App. Navigate to **Setup > App Manager**, edit the target Lightning App, go to **Utility Items**, and add **Open CTI Softphone**. The utility item pulls its adapter URL from the assigned Call Center definition — you do not specify a URL directly in the utility item. Width, height, and panel label are configurable here. The utility bar item is desktop-only; the Salesforce mobile app does not render utility bars.

### Call Logging with saveLog

`sforce.opencti.saveLog` creates a Task record. The `value` parameter is an object where keys are Task API field names. At minimum pass `Subject`, `CallDurationInSeconds`, `CallType`, `Status`, and `WhoId` or `WhatId`. The Task `ActivityDate` defaults to today. Logging should occur on call end, not call start, to capture accurate duration. If the adapter calls `saveLog` before `enableClickToDial` resolves, the API may return an error; always gate saveLog on a confirmed active session.

### lightning-click-to-dial LWC Component

Salesforce provides the `lightning-click-to-dial` base component for use in record pages and list views. It renders a phone number as a clickable link that fires the Open CTI `onClickToDial` listener registered in the adapter. The component accepts `value` (phone string) and `record-id` attributes. Using this component is preferred over raw `<a href="tel:...">` links because it participates in the Open CTI event system and respects `enableClickToDial`/`disableClickToDial` state.

---

## Common Patterns

### Pattern: Enabling Click-to-Dial on Adapter Load

**When to use:** The adapter must activate click-to-dial once when the softphone iframe loads so phone fields become clickable across the entire org session.

**How it works:**

1. On `DOMContentLoaded`, call `sforce.opencti.enableClickToDial` with a callback.
2. Inside the callback, check `response.success`. If `true`, register the `onClickToDial` listener.
3. In the `onClickToDial` listener, extract `number` and `recordId` to initiate the telephony call.
4. Call `sforce.opencti.disableClickToDial` immediately after the call connects to prevent double-dials.

**Why not the alternative:** Registering `onClickToDial` before `enableClickToDial` succeeds causes the listener to register against a deactivated state, meaning clicks on phone fields silently do nothing.

### Pattern: Logging a Call as a Closed Task

**When to use:** Every call end should produce a Task record so supervisors and agents have a call history on the related record.

**How it works:**

1. On telephony call-end event (vendor-specific), capture duration and call direction.
2. Call `sforce.opencti.saveLog` with a `value` object containing at minimum: `Subject`, `Status: 'Completed'`, `CallType` (`'Inbound'` or `'Outbound'`), `CallDurationInSeconds`, and either `WhoId` (Contact/Lead) or `WhatId` (Account/Case/Opportunity).
3. In the callback, check `response.success`. Surface an error toast to the agent if logging fails.

**Why not the alternative:** Creating a Task via Apex REST from the telephony vendor backend requires OAuth token management and server-side configuration. `saveLog` runs in the user's browser session with their permissions, is simpler to implement, and creates the Task associated with the correct Call Center activity.

### Pattern: Screen Pop on Inbound Call

**When to use:** When an inbound call arrives with a known caller ID, the agent's browser should automatically navigate to the caller's Contact or Case record.

**How it works:**

1. On inbound call event, perform an ANI (caller ID) lookup — typically via a Salesforce REST API callout or vendor webhook.
2. Once the Salesforce record ID is known, call `sforce.opencti.screenPop` with `type: 'SOBJECT'` and `params: {recordId: '<18-char-id>'}`.
3. If no record is found, use `type: 'SEARCH'` with `params: {searchParams: '<phone-number>'}` to open a search results page.

---

## Decision Guidance

| Situation | Recommended Approach | Reason |
|---|---|---|
| New third-party telephony integration with browser softphone | Open CTI JavaScript API + `callcenter.xml` | Native platform path; no AppExchange package required for basic integration |
| Need Amazon Connect deep integration (transcription, AI coaching) | Service Cloud Voice — do NOT use Open CTI | Service Cloud Voice is the supported path for Amazon Connect; Open CTI does not provide real-time transcription hooks |
| Rendering a phone field that users can click to call | `lightning-click-to-dial` LWC component | Participates in CTI enable/disable state; preferred over bare anchor tags |
| Need call duration and direction on Task record | `saveLog` with `CallDurationInSeconds` + `CallType` | These are standard Activity fields; `saveLog` maps directly to them |
| Softphone panel is too small for the adapter UI | `setSoftphonePanelHeight` called at adapter load | Dynamic resize after DOM measurement is the correct approach |

---

## Recommended Workflow

Step-by-step instructions for an AI agent or practitioner working on this task:

1. **Verify prerequisites** — Confirm the telephony vendor adapter page URL is available over HTTPS, the domain is added to CSP Trusted Sites and CORS allowed-origins in Setup, and the user has a valid Salesforce license that supports Call Centers.
2. **Author and import callcenter.xml** — Create the Call Center definition file with `CTIVersion` set to `4.0` and `AdapterUrl` pointing to the hosted adapter page. Import via Setup > Call Centers. Assign relevant users to the Call Center.
3. **Register the utility item** — In App Manager, add the Open CTI Softphone utility item to the target Lightning App. Set appropriate panel width and height. Verify the softphone panel renders in the app.
4. **Implement the adapter page** — In the hosted HTML file, call `sforce.opencti.enableClickToDial` on load, register the `onClickToDial` listener, implement `screenPop` for inbound call ANI lookup, and wire call-end events to `saveLog`.
5. **Test click-to-dial and screen pop** — In a sandbox, click a phone field and confirm the `onClickToDial` callback fires with correct `number` and `recordId`. Trigger a screen pop and confirm navigation to the correct record.
6. **Test call logging** — End a test call and verify a Task record with the correct `Subject`, `CallDurationInSeconds`, `CallType`, and related `WhoId`/`WhatId` is created.
7. **Review CSP and CORS** — Confirm no browser console errors related to blocked frames, CORS rejections, or `sforce is not defined` errors before deploying to production.

---

## Review Checklist

Run through these before marking work in this area complete:

- [ ] `callcenter.xml` uses `CTIVersion` 4.0 (not 3.0) and a valid HTTPS `AdapterUrl`
- [ ] Adapter page does not self-load the Open CTI script — it relies on platform injection
- [ ] `enableClickToDial` is called once on adapter load before `onClickToDial` is registered
- [ ] `saveLog` passes at minimum `Subject`, `Status: 'Completed'`, `CallType`, and `WhoId` or `WhatId`
- [ ] `screenPop` falls back to `type: 'SEARCH'` when no record ID is available
- [ ] Adapter domain is in CSP Trusted Sites and CORS allowed-origins
- [ ] Users are assigned to the Call Center in Setup before testing

---

## Salesforce-Specific Gotchas

Non-obvious platform behaviors that cause real production problems:

1. **CTIVersion mismatch silently breaks the adapter** — Using `CTIVersion` 3.0 in `callcenter.xml` loads the Classic Open CTI API, not the Lightning API. The `sforce.opencti` namespace is undefined and all calls fail without a clear error message in the Lightning console.
2. **enableClickToDial must resolve before onClickToDial is registered** — Calling `onClickToDial` before the `enableClickToDial` callback succeeds means the listener registers against a deactivated state. Phone field clicks are silently swallowed. Always nest `onClickToDial` inside the `enableClickToDial` success callback.
3. **saveLog requires an active Call Center assignment** — If the logged-in user is not assigned to a Call Center, `saveLog` returns `success: false` with the error `User is not assigned to a Call Center`. The call still appears to complete with no visible UI error unless the adapter explicitly checks `response.success`.
4. **Utility bar is desktop-only** — The Open CTI softphone panel does not render in the Salesforce mobile app or in Salesforce Classic. Agents using mobile or Classic see no softphone. This is a platform constraint, not a configuration error.
5. **CSP and CORS must both be configured** — Adding the adapter domain to CSP Trusted Sites alone is not sufficient. The domain also requires an entry in Setup > CORS Allowed Origins List (or the adapter URL must be framed correctly). Missing CORS causes `sforce is not defined` errors in the browser console because the platform cannot inject the API into a non-allowed origin.

---

## Output Artifacts

| Artifact | Description |
|---|---|
| `callcenter.xml` | Call Center definition file to import into Setup > Call Centers |
| Adapter HTML page | Hosted page implementing Open CTI JavaScript API methods |
| Lightning App utility item config | Step-by-step configuration for the softphone utility bar item |
| Task logging snippet | `saveLog` implementation with correct field mapping |
| `lightning-click-to-dial` usage snippet | LWC component markup for phone field rendering |

---

## Related Skills

- `admin/app-and-tab-configuration` — configuring the Lightning App utility bar where the softphone item lives
- `architect/service-cloud-architecture` — when to use Service Cloud Voice vs Open CTI at the architecture level

Related Skills

lwr-site-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when building or customizing sites on the Lightning Web Runtime (LWR) in Experience Cloud — including component authoring, custom theming with --dxp hooks, layout components, and publish lifecycle management. Trigger keywords: build LWR site Experience Cloud, Lightning Web Runtime custom theme, LWR component development community, Build Your Own LWR template, Microsite LWR, lightningCommunity__Theme_Layout, --dxp styling hooks. NOT for Aura-based communities (Build Your Own Aura template). NOT for standard Experience Builder drag-and-drop configuration without code.

data-cloud-activation-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when building developer-driven Data Cloud activation surfaces: webhook Data Action Targets with HMAC-SHA256 signing, Salesforce Platform Event data actions, Data Cloud-Triggered Flows on DMO insert, or Marketing Cloud journey triggers. Triggers on: webhook data action target, Data Cloud triggered Flow not firing, HMAC secret key for data action, platform event from Data Cloud, DMO insert trigger. NOT for configuring standard admin-level Activation Targets (SFTP, ad platform segment publishing, CRM segment activation) — those require admin configuration skills, not this developer extensibility skill.

unlocked-package-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when designing, creating, versioning, or installing unlocked packages: package directory configuration in sfdx-project.json, namespace management, package dependencies, version lifecycle (beta vs. released), ancestor versions, installation keys, and subscriber installation via sf CLI or Package Install UI. NOT for 2GP managed packages (ISV packaging with namespaces, push upgrades, or AppExchange listings), 1GP managed packages, change set deployments, or scratch org setup.

package-development-strategy

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when deciding between Salesforce package development approaches — unmanaged, unlocked, 1GP managed, or 2GP managed — including namespace selection, ISV distribution requirements, upgrade path design, and AppExchange packaging strategy. Trigger keywords: should I use managed or unlocked package, Salesforce package type selection, 2GP vs 1GP managed package, namespace decision Salesforce, ISV AppExchange packaging, unlocked package strategy. NOT for individual package creation steps, scratch org setup, or day-to-day package version build commands.

multi-package-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Designing, orchestrating, and maintaining multi-package architectures in Salesforce DX: dependency DAG design, layered package decomposition, install ordering, cross-package API contracts, mono-repo vs. multi-repo layout, and CI/CD pipeline sequencing for projects with two or more unlocked or managed packages. NOT for single-package creation or versioning (see unlocked-package-development), 2GP managed-package ISV workflows (see second-generation-managed-packages), or change-set deployments.

managed-package-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when building or maintaining Salesforce first-generation managed packages (1GP) for ISV distribution — covers namespace registration, packaging org structure, PostInstall/UninstallHandler Apex interface, push upgrades, Flow version management, and subscriber org considerations. NOT for second-generation managed packages (2GP), unlocked packages, or AppExchange listing setup.

ampscript-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when writing, debugging, or reviewing AMPscript in Marketing Cloud email bodies, subject lines, preheaders, SMS, push notifications, or Cloud Pages — including Lookup/LookupRows data retrieval, IF/ELSEIF conditional blocks, FOR loops over rowsets, and inline personalization. NOT for Server-Side JavaScript (SSJS), REST API calls from content, SQL Query Activities, or Journey Builder configuration.

einstein-discovery-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when integrating Einstein Discovery predictions into Salesforce apps, automating bulk scoring jobs, deploying stories as prediction definitions, managing models via API, or querying prediction history. Trigger keywords: Einstein Discovery, smartdatadiscovery, predict endpoint, bulk scoring job, model refresh job, prediction definition, story deployment, regression prediction, multiclass prediction, CRM Analytics ML. NOT for CRM Analytics dashboard design, TCRM dataset management, Einstein Prediction Builder binary classification (which requires no CRM Analytics license), or Einstein Next Best Action recommendation strategies.

saql-query-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when writing, debugging, or optimizing SAQL queries in CRM Analytics — covering pipeline syntax, aggregation, windowing functions (rank, dense_rank, moving_average), cogroup joins, rollup subtotals, piggyback queries, and REST API execution. NOT for SOQL, standard Salesforce reports, SQL databases, or any non-CRM Analytics query language.

analytics-dataflow-development

8
from PranavNagrecha/AwesomeSalesforceSkills

Use this skill when building, debugging, or optimizing CRM Analytics dataflows — defining node types (sfdcDigest, Append, Augment, computeExpression, computeRelative, Flatten, dim2mea, sfdcRegister), scheduling runs, handling run failures, and tuning performance. NOT for standard data processing outside CRM Analytics, for recipe-based data prep, or for SAQL dashboard query tuning.

xss-and-injection-prevention

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when writing or reviewing Visualforce pages, Apex controllers, or LWC components that output user-supplied data, build dynamic queries, or construct HTTP responses. Triggers: 'XSS in Visualforce', 'SOQL injection vulnerability', 'how to encode output in Apex', 'JSENCODE Visualforce', 'open redirect prevention'. NOT for Apex CRUD/FLS enforcement (use soql-security or apex-crud-and-fls), NOT for Shield encryption (use shield-encryption-key-management), NOT for AppExchange security review process (use secure-coding-review-checklist).

visualforce-security-and-modernization

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when hardening or modernizing legacy Visualforce pages — covers the platform CSRF token model and when disabling it is a security regression, view state encryption guarantees and the 170 KB ceiling, FLS/CRUD enforcement gaps on `<apex:outputField>` and on getters that return sObjects, `<apex:includeScript>` interaction with the org Content Security Policy, hosting LWC inside a VF page via `lightning:container` / `lightning-out`, and the retire-vs-harden-vs-leave-alone decision for an inventory of legacy pages. Triggers: 'should I rewrite this Visualforce page in LWC', 'CSRF protection disabled on Visualforce page is that safe', 'community user sees a field they should not on a Visualforce page', 'view state encryption is that enough for sensitive data', 'how do I host an LWC inside a Visualforce page', 'apex:dynamicComponent and apex:actionFunction safe to keep'. NOT for greenfield Visualforce architecture (use apex/visualforce-fundamentals — controller types, view state pattern selection, PDF rendering); NOT for Visualforce email template authoring (use apex/visualforce-email-templates if/when that skill is authored); NOT for general Apex security review across triggers and async (use apex/soql-security and security/secure-coding-review-checklist).