Skip to main content

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​

PropertyTypeDescription
direction"inbound" \| "outbound"Which side initiated the call.
remoteIdentitystring \| undefinedFar end's SIP URI / E.164 (when the server provided it).
customHeadersReadonly<Record<string, string>>Inbound only β€” X-2Chat-* SIP headers attached to the incoming INVITE, with the prefix stripped.
sessionanyThe 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).

EventPayloadWhen it fires
ringingvoidOutbound: far side is alerting (180/183).
acceptedvoidThe call was answered and media is flowing.
disconnect{ reason?: string }Call ended β€” caller hung up, callee declined, network drop, etc.
errorVoiceSDKErrorCall-scoped failure. May also be followed by disconnect.
mutebooleanLocal mute state changed.
holdbooleanLocal hold state changed.
qualityQualitySamplePeriodic 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​