English
English
Appearance
English
English
Appearance
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.
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.
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
}connect Documented in the Static factory section above. Returns a phone instance used for the remaining methods.
disconnect 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.
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.
call 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_ringing → call_answered → call_hangup.
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'.
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').
answer 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.
phone.on('incoming_call', async ({ call }) => {
if (myUI.shouldAutoAnswer()) await call.answer()
})reject / hangup call.hangup(): Promise<void>Ends a call in any state. The SDK picks the right SIP method automatically:
| State | SIP method |
|---|---|
originating (outbound, not yet ringing) | CANCEL |
ringing inbound (not yet answered) | REJECT (response 486 / 603) |
answered | BYE |
reject vs hangup
The SDK exposes a single call.hangup() that is state-aware. If you want explicit "reject incoming" semantics, alias it:
const reject = (call: ZorioCall) => call.hangup()mute / unmute 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.
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()
}hold / unhold 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).
await call.hold()
// ... handle something else
await call.unhold()sendDtmf call.sendDtmf(digits: string): Promise<void>Sends DTMF tones via RFC 2833. Supports characters 0-9, *, #, A-D. Other characters throw code = 'invalid_dtmf'.
// 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 #
})transfer 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.
// 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 + '#').
phone.on() / phone.off() are not part of the 12 primary methods but are essential:
phone.on<E extends EventName>(event: E, handler: (payload: EventPayload<E>) => void): void
phone.off<E extends EventName>(event: E, handler?: Function): voidSee all 12 events in the Events page.
Useful properties for observing state without waiting for an event:
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 // booleanEvery method throws ZorioWebphoneError:
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.
try {
const call = await phone.call('xxx')
} catch (err) {
if (err.code === 'invalid_target') {
alert('Invalid phone number')
} else {
console.error(err)
}
}| # | Method | Scope | Purpose |
|---|---|---|---|
| 1 | connect | static | Bootstrap the SDK and register SIP |
| 2 | disconnect | phone | Tear everything down; use on logout |
| 3 | call | phone | Start an outbound call |
| 4 | hangup | call | Cancel/Bye an in-progress call |
| 5 | answer | call | Answer an inbound call |
| 6 | reject | call | Alias for hangup when an inbound is not yet answered |
| 7 | mute | call | Mute the microphone |
| 8 | unmute | call | Unmute the microphone |
| 9 | hold | call | Hold (re-INVITE sendonly) |
| 10 | unhold | call | Resume |
| 11 | sendDtmf | call | Send IVR tones |
| 12 | transfer | call | Blind transfer (Phase 2) |
See also: