Call
A Call represents one phone call β inbound or outbound. You don't construct it directly: outbound calls come from device.connect(...) and inbound calls arrive as the payload of the incoming event.
device.on("incoming", (call) => {
if (confirm(`Incoming call from ${call.remoteIdentity}`)) call.accept();
else call.reject();
});
const call = await device.connect({ to: "+15551234567", from: "+15550000000" });
Propertiesβ
| Property | Type | Description |
|---|---|---|
direction | "inbound" \| "outbound" | Which side initiated the call. |
remoteIdentity | string \| undefined | Far end's SIP URI / E.164 (when the server provided it). |
customHeaders | Readonly<Record<string, string>> | Inbound only β X-2Chat-* SIP headers attached to the incoming INVITE, with the prefix stripped. |
session | any | The raw JsSIP RTCSession. Escape hatch β prefer the methods below. |
Methodsβ
getStatus()β
call.getStatus(): "pending" | "ringing" | "accepted" | "disconnected" | "failed"
Current call state. Transitions also surface as events.
accept()β
await call.accept(): Promise<void>
Inbound only. Answer the call with audio.
reject(statusCode?)β
call.reject(statusCode = 486): void
Inbound only. Reject with a SIP status code (486 Busy Here by default; 603 Decline is common for "do not disturb").
disconnect()β
await call.disconnect(): Promise<void>
Hang up the call. Works on either direction and on calls in any state past pending.
mute(mute)β
call.mute(true); // mute
call.mute(false); // unmute
Toggle the local microphone. Emits mute with the new state. Check with call.isMuted().
hold(hold)β
call.hold(true);
call.hold(false);
Put the call on hold (sends a=sendonly SDP) or take it off hold. Emits hold with the new state. Check with call.isOnHold().
sendDigits(digits)β
call.sendDigits("1");
call.sendDigits("1234#");
Send DTMF tones (RFC 2833). Multiple characters are sent in order. Allowed: 0-9, *, #, and A-D.
setInputDevice(deviceId) / setOutputDevice(deviceId)β
await call.setInputDevice(micId);
await call.setOutputDevice(speakerId);
Swap audio devices on an active call. The output device requires browser support for HTMLMediaElement.setSinkId (Chromium-based browsers).
isMuted() / isOnHold()β
call.isMuted(); // boolean
call.isOnHold(); // boolean
Eventsβ
Subscribe with call.on(name, handler).
| Event | Payload | When it fires |
|---|---|---|
ringing | void | Outbound: far side is alerting (180/183). |
accepted | void | The call was answered and media is flowing. |
disconnect | { reason?: string } | Call ended β caller hung up, callee declined, network drop, etc. |
error | VoiceSDKError | Call-scoped failure. May also be followed by disconnect. |
mute | boolean | Local mute state changed. |
hold | boolean | Local hold state changed. |
quality | QualitySample | Periodic media-quality snapshot (once per second while connected). |
QualitySampleβ
interface QualitySample {
timestamp: number; // epoch ms when the sample was taken
rttMs?: number; // round-trip time in ms
jitterMs?: number; // jitter in ms
outboundLoss?: number; // 0..1, fraction lost on send
inboundLoss?: number; // 0..1, fraction lost on receive
mosEstimate?: number; // 1..5 MOS estimate
selectedCodec?: string; // "OPUS", "PCMU", ...
localCandidateType?: string;
remoteCandidateType?: string;
}
Use this to render a signal-strength indicator or warn the user about a degraded line.
Call lifecycleβ
ββββββββββ
outbound βpending β
βββββββββΊβ β
βββββ¬βββββ
β INVITE sent
βΌ
ββββββββββ remote answer ββββββββββββ BYE / failure ββββββββββββββββ
βringing β βββββββββββββββΊ β accepted β βββββββββββββββΊ β disconnected β
ββββββββββ ββββββββββββ ββββββββββββββββ
β
β failure during media
βΌ
ββββββββββ
β failed β
ββββββββββ
Inbound calls start in pending until you accept() (β accepted) or reject() (β disconnected).
Tagging calls with custom headersβ
When placing a call, anything you pass in params becomes an X-2Chat-<Key> SIP header on the INVITE. Use it to attach metadata your CDR pipeline or webhook handler can read back:
const call = await device.connect({
to: "+15551234567",
from: "+15550000000",
params: {
ticketId: "INC-4815",
operator: "ada",
},
});
On inbound calls, the SDK exposes those headers (with the X-2Chat- prefix stripped) on call.customHeaders.
See alsoβ
- Device API β where calls come from.
- Errors β
CALL_FAILED,MUTE_FAILED,HOLD_FAILED, etc. - Phone Calls REST API β query CDRs for calls placed via the SDK.