Skip to main content
Freshdesk A calling panel that puts Vobiz telephony inside Freshdesk. Agents place and receive real phone calls from the browser and play back recordings without leaving the helpdesk. Source code: vobiz-ai/Vobiz-Freshdesk-Calling — the Freshworks app used throughout this guide, plus a zero-dependency mock backend so you can develop the panel without a Vobiz account.
Scope: inbound and outbound, with browser audio over WebRTC. This installs as a custom app on your own account — there is no Marketplace review to wait for, and it is usually live within about thirty minutes of upload.
This replaces your current telephony. Freshdesk allows only one CTI app to be active at a time, and Freshcaller cannot be enabled alongside one. Installing this takes over from whatever telephony the account uses today.

What you get

How it works

Freshdesk’s telephony surface is small. It exposes exactly one event to a calling app:
It fires when an agent clicks a phone number on a ticket or contact page. Installing a CTI app is what makes those numbers clickable in the first place — the app registers nothing to enable it. There is no inbound-call event, no agent-presence event and no call-ended event. Everything else flows the other way: the app tells Freshdesk what happened through client.interface.trigger and the Freshdesk REST API. The depth of any Freshdesk CTI integration is therefore a function of how much the app chooses to write back. The whole Freshworks surface this app uses today:

The outbound call flow

Five-step outbound call flow: the agent clicks Call, the panel POSTs /start-call, the backend has Vobiz dial the customer first, the customer answering triggers the /answer webhook which returns a Dial User element, and Vobiz then dials into the agent's browser where JsSIP auto-answers.Five-step outbound call flow: the agent clicks Call, the panel POSTs /start-call, the backend has Vobiz dial the customer first, the customer answering triggers the /answer webhook which returns a Dial User element, and Vobiz then dials into the agent's browser where JsSIP auto-answers.
The order cannot be reversed. The Vobiz REST API cannot originate a call to a registered WebRTC endpoint — it returns Endpoint Not Registered. So the customer is dialled first and the agent is bridged in second.Tell your agents about the ringback. A customer who answers and hears a second of tone sometimes hangs up, thinking the call failed.

Inbound calls

The agent clicks Enable inbound calls to this panel once. The backend creates (or reuses) a Vobiz application pointing at its own /inbound-answer webhook and attaches the agent’s selected number to it. Real calls to that number then hit /inbound-answer, which dials sip:… — landing on the same auto-answer path as the outbound bridge leg. Keep the Freshdesk tab open; the panel has to stay registered to receive the call.

Requirements

The calling backend

The app is a client. It holds no Vobiz credentials and cannot place a call on its own — it talks to a calling backend that you run, which holds the account credentials and drives the Vobiz REST API.
The backend is not in the repository. Its complete contract is in docs/backend-contract.md. Read it before you start — the obvious implementation of the recording endpoint is unsafe, and the document explains why.
Two reasons the backend exists, both non-negotiable:
  1. Vobiz Auth Tokens are account-level API credentials. Never expose X-Auth-Token in client-side code — see authentication. The backend holds the token and the browser never sees it.
  2. An <audio> element cannot send authentication headers. Recordings are protected, so playback needs a server-side step.
Everything below is relative to the backend_url installation parameter. The app requires HTTPS and strips trailing slashes.

Security requirements

The app sends agentId as a plain string in the path, body or query. If your backend trusts that string, every browser route above is unauthenticated and the agentId is guessable. Somebody who learned your backend URL could then read SIP passwords, place calls billed to your account, rewrite your inbound number routing, and download every recording.
A backend implementing this contract must:
  • Authenticate every request. Do not treat agentId as proof of identity — issue a per-agent token at login and require it.
  • Not serve long-lived SIP passwords. Mint short-lived credentials, or gate /agent/{agentId} behind the same authentication.
  • Sign recording URLs with a short expiry, and verify the signature on every request. An endpoint that streams any recording to anyone holding the URL is a breach waiting to happen — and /recordings hands out the IDs.
  • Scope CORS to the Freshdesk origin. Never Access-Control-Allow-Origin: *.
  • Store Auth Tokens encrypted at rest, and never log them.
  • Serve over HTTPS with a stable hostname.
If you are adapting an internal prototype, assume it does none of these.

Step 1: Pack the app

The Freshworks CLI is pinned to a Node version, with a separate CLI build per Node major. Install the pair the app declares in manifest.json:
The installable zip lands in dist/.
fdk pack enforces Freshworks’ 80% coverage floor on every metric, and additionally wants local simulation coverage, which is only produced by running the app inside a real Freshdesk account via ?dev=true. For local builds use fdk pack --skip-coverage; do not use that flag for a Marketplace submission.

Step 2: Create the custom app

  1. In Freshdesk, go to Admin → Apps.
  2. Click Go to Developer Portal.
  3. Click Create New App and choose Custom App.
  4. Select your Freshdesk account.
  5. Upload the zip from dist/.
  6. Fill in the app details — name, description, and your team’s support email.
  7. Save and Publish, then Promote to Live.

Step 3: Install and configure

  1. Back in Freshdesk, go to Admin → Apps → Manage Apps.
  2. Filter to Custom.
  3. Find the app and click Install.
  4. Fill in the settings:
All three can be changed later without reinstalling, under Admin → Apps → Manage Apps → Custom → Settings → Configure.

Step 4: Open the panel

Open any agent page and look for the Vobiz icon at the bottom left. The panel loads on all agent pages, not only tickets — if the icon does not appear, hard refresh. Then:
  1. Log in with your Vobiz Auth ID and Auth Token, and pick the number to call from. These are entered by the agent at runtime and are not stored by the app.
  2. Make a call by typing a number and clicking Call, or by clicking any phone number in Freshdesk.
  3. The first call prompts for microphone permission, since audio runs through the browser.
  4. Receive calls by clicking Enable inbound calls to this panel once.

Develop without a Vobiz account

The repository ships a mock backend that implements the whole contract with fake data, so the panel can be developed without placing a real call.
Open a Freshdesk page with ?dev=true appended, then set the app’s settings at http://localhost:10001/custom_configs: Any Auth ID and Auth Token are accepted. Use fail as the Auth ID to exercise the error path. The mock backend’s README covers pointing it at a real Vobiz SIP endpoint to test actual audio.

Tests

The tests load the real app/scripts/app.js and the real markup from app/index.html into jsdom with the Freshworks SDK, JsSIP and fetch faked, so they exercise the code that ships rather than a copy of it. The whole app is three files — app/index.html, app/scripts/app.js and app/styles/style.css. No build step, no framework.

Binding inbound audio

Worth knowing before you touch the SIP code. For an incoming session, JsSIP has not built the RTCPeerConnection yet — session.connection is null until the call is answered. Dereferencing it inside the newRTCSession handler throws, and because the throw happens inside that handler it aborts before .answer() runs. The symptom is misleading: the browser silently never picks up, Vobiz rings the endpoint until it times out, and the far end is never connected. Bind through the peerconnection event instead:

Troubleshooting

WebRTC inside a Freshdesk iframe. The app registers over SIP-over-WebSocket from inside a Freshworks-hosted iframe, and Freshworks does not publish the Content-Security-Policy applied to app iframes. If your network or the platform blocks the WebSocket, registration fails and the panel reports it. Test on one account before rolling out widely.
If the backend needs allowlisting on your side, see IP whitelisting.

Scope today

The panel is a softphone that lives inside Freshdesk. Click-to-call, outbound, inbound and recording playback all work today. Writing back into Freshdesk’s data — creating tickets from calls, logging calls as ticket notes, popping the contact record on an inbound call, attaching recordings — is on the roadmap in the repository’s CHANGELOG. Hold, mute, transfer and conference are not built, and one agent identity is set per installation by an admin. Two constraints to plan around if you extend it:
  • The CTI sidebar has no page context. client.data.get("ticket") is not available in cti_global_sidebar. Knowing which ticket the agent is viewing needs a separate ticket_background instance, which shares no JavaScript state — so coordination goes through $db or the backend.
  • The Freshdesk API key should be a secure iparam, which means calls using it must go through client.request.invokeTemplate rather than fetch. Secure iparams are only substituted into request-template headers and are never readable from front-end JavaScript.

Next steps