Skip to content

API methods

The ZorioWebphone class exposes 12 primary methods. The first four live on the webphone instance (phone.*); the eight call-control methods live on the call object (call.* — returned by phone.call() or via the incoming_call event payload).

Calling convention

Every method is async and returns a Promise. await it to catch errors and ensure state is synchronized before chaining the next call.

1. Static factory

ts
ZorioWebphone.connect(opts: ConnectOptions): Promise<ZorioWebphone>
ZorioWebphone.diagnose(opts: { apiBase: string; token: string }): Promise<DiagnoseResult>

connect() bootstraps: calls /api/webphone/sip-config → creates a SIP.js UserAgent → registers → mounts the UI (if mount is set). diagnose() runs a preflight on HTTPS / mic / WSS / STUN / TURN — call it before connect() for debugging.

ts
interface ConnectOptions {
  apiBase:              string
  token:                string
  mount?:               string | HTMLElement
  layout?:              'docked' | 'overlay' | 'fullscreen'
  theme?:               'light' | 'dark' | 'auto'
  locale?:              'vi' | 'en'
  autoRegister?:        boolean
  ringtoneUrl?:         string
  audioOutputDeviceId?: string
  audioInputDeviceId?:  string
  tokenResolver?:       () => Promise<string>
  debug?:               boolean
  onError?:             (err: ZorioWebphoneError) => void
}

2. Instance methods — connection and control

2.1 connect

Documented in the Static factory section above. Returns a phone instance used for the remaining methods.

2.2 disconnect

ts
phone.disconnect(): Promise<void>

Closes every resource: unregister SIP, close the WebSocket, remove the UI DOM (if embedded), clear the audio element, cancel every reconnect timer. Must be called when the user logs out of your app.

ts
async function onLogout() {
  await phone.disconnect()
  redirectTo('/login')
}

Pagehide is handled for you

The SDK installs addEventListener('pagehide', ...) to unregister cleanly when the user closes the tab. You only need to call disconnect() manually for explicit logout flows.

2.3 call

ts
phone.call(number: string, options?: CallOptions): Promise<ZorioCall>

interface CallOptions {
  campaignId?:  number
  leadId?:      number
  customerId?:  number
  recording?:   boolean
  callerName?:  string
  meta?:        Record<string, unknown>
}

Initiates an outbound call. Returns a ZorioCall immediately after the SIP INVITE is sent (state originating); the call continues its lifecycle through events call_ringingcall_answeredcall_hangup.

ts
const call = await phone.call('0912345678', {
  campaignId: 36,
  leadId: 78912,
  recording: true,
  meta: { source: 'crm-quickdial' },
})
console.log('Call UUID:', call.uuid)

Number format

The number format follows the customer's Zorio PBX dial plan. The default is E.164 (+84912345678) or domestic (0912345678). Unparseable numbers throw ZorioWebphoneError with code = 'invalid_target'.

3. Call-control methods (on ZorioCall)

The following methods are invoked on the ZorioCall object obtained from phone.call(), the incoming_call payload, or via phone.calls.find(c => c.state === 'answered').

3.1 answer

ts
call.answer(): Promise<void>

Answer an inbound call. Throws code = 'not_incoming' if invoked on an outbound call. Automatically attaches the mic track and remote audio.

ts
phone.on('incoming_call', async ({ call }) => {
  if (myUI.shouldAutoAnswer()) await call.answer()
})

3.2 reject / hangup

ts
call.hangup(): Promise<void>

Ends a call in any state. The SDK picks the right SIP method automatically:

StateSIP method
originating (outbound, not yet ringing)CANCEL
ringing inbound (not yet answered)REJECT (response 486 / 603)
answeredBYE

reject vs hangup

The SDK exposes a single call.hangup() that is state-aware. If you want explicit "reject incoming" semantics, alias it:

ts
const reject = (call: ZorioCall) => call.hangup()

3.3 mute / unmute

ts
call.mute():   Promise<void>
call.unmute(): Promise<void>

Toggle the local microphone. Does NOT affect remote audio. The state is mirrored on call.muted: boolean and emitted via muted / unmuted events.

ts
muteBtn.onclick = async () => {
  const active = phone.calls.find(c => c.state === 'answered')
  if (!active) return
  if (active.muted) await active.unmute()
  else              await active.mute()
}

3.4 hold / unhold

ts
call.hold():   Promise<void>
call.unhold(): Promise<void>

Hold the call via SIP re-INVITE with SDP a=sendonly. This is a standards-compliant hold so the callee hears the Zorio PBX music-on-hold (if enabled on the profile).

ts
await call.hold()
// ... handle something else
await call.unhold()

3.5 sendDtmf

ts
call.sendDtmf(digits: string): Promise<void>

Sends DTMF tones via RFC 2833. Supports characters 0-9, *, #, A-D. Other characters throw code = 'invalid_dtmf'.

ts
// Navigate an IVR after the callee answers (wait 1s for the IVR prompt)
phone.on('call_answered', async ({ call }) => {
  await new Promise(r => setTimeout(r, 1000))
  await call.sendDtmf('1')      // pick branch 1
  await call.sendDtmf('2#')     // pick 2 then #
})

3.6 transfer

ts
call.transfer(target: string): Promise<void>

Blind transfer — moves the call to target (extension/number) without consult. The state moves to closed once the Zorio PBX accepts the SIP REFER.

ts
// User clicks "Transfer to extension 2002"
await call.transfer('2002')

v1.0 — not yet supported

transfer() and the consult/attended transfer method currently throw code = 'not_implemented' in v1.0. Scheduled for Phase 2. Partners who need transfer today can fall back on the Zorio PBX *8 blind-transfer feature code → press DTMF via sendDtmf('*8' + target + '#').

4. Event subscription

phone.on() / phone.off() are not part of the 12 primary methods but are essential:

ts
phone.on<E extends EventName>(event: E, handler: (payload: EventPayload<E>) => void): void
phone.off<E extends EventName>(event: E, handler?: Function): void

See all 12 events in the Events page.

5. Read-only properties

Useful properties for observing state without waiting for an event:

ts
phone.status        // 'connecting' | 'registered' | 'unregistered' | 'failed'
phone.extension     // the registered SIP extension number
phone.user          // UserProfile | null
phone.calls         // ZorioCall[] — every active call

// On ZorioCall:
call.uuid           // Zorio PBX UUID (links to the CDR)
call.direction      // 'incoming' | 'outgoing'
call.state          // 'originating' | 'ringing' | 'answered' | 'wrap_up' | 'closed'
call.number         // remote number (E.164)
call.startedAt      // Date — when the call was created
call.answeredAt     // Date | null — when it connected
call.endedAt        // Date | null
call.muted          // boolean
call.onHold         // boolean

6. Error handling

Every method throws ZorioWebphoneError:

ts
class ZorioWebphoneError extends Error {
  code:     string
  details?: Record<string, unknown>
}

Common error codes: bootstrap_failed, webphone_disabled, incomplete_sip_config, invalid_target, not_incoming, invalid_dtmf, audio_autoplay_blocked, reconnect_failed, not_implemented, not_bootstrapped.

ts
try {
  const call = await phone.call('xxx')
} catch (err) {
  if (err.code === 'invalid_target') {
    alert('Invalid phone number')
  } else {
    console.error(err)
  }
}

7. Summary table of the 12 primary methods

#MethodScopePurpose
1connectstaticBootstrap the SDK and register SIP
2disconnectphoneTear everything down; use on logout
3callphoneStart an outbound call
4hangupcallCancel/Bye an in-progress call
5answercallAnswer an inbound call
6rejectcallAlias for hangup when an inbound is not yet answered
7mutecallMute the microphone
8unmutecallUnmute the microphone
9holdcallHold (re-INVITE sendonly)
10unholdcallResume
11sendDtmfcallSend IVR tones
12transfercallBlind transfer (Phase 2)

See also:

Cấp phép theo điều khoản sử dụng của Zorio.