Skip to main content
This guide covers the daily-js APIs for SIP and PSTN telephony. For an overview of how dial-in and dial-out work across all APIs, see the dial-in/dial-out guide.

Prerequisites

Before calling any telephony methods:
  • Your Daily account must have a credit card on file.
  • For dial-out, the room must have enable_dialout: true and your account must be approved for dial-out.
  • The local participant must join with an owner meeting token.

Dial-out

Starting a session

startDialOut() initiates an outbound call to a SIP URI or PSTN phone number. The called party joins the room as a regular participant.
const { session } = await call.startDialOut({
  sipUri: 'sip:conference@pbx.example.com',
  displayName: 'Conference Bridge',
  video: false,
  codecs: { audio: ['OPUS', 'G722'] },
});

const sessionId = session?.sessionId;
The returned sessionId identifies this telephony session. Hold onto it — you’ll need it for stopDialOut(), sendDTMF(), and transfer methods. See startDialOut() for the full parameter reference, including videoSettings for SIP video calls and codec defaults.

Stopping a session

await call.stopDialOut({ sessionId });

Sending DTMF tones

sendDTMF() sends touch-tone signals into an active session — commonly used to navigate IVR menus:
await call.sendDTMF({
  sessionId,
  tones: '1234#',
  method: 'auto',
  digitDurationMs: 100,
});
The method parameter controls how tones are signaled:
  • 'telephone-event' — RTP telephone-event packets per RFC 4733 (most compatible)
  • 'sip-info' — SIP INFO messages per RFC 6086
  • 'auto' — Daily picks the best method for the connection

Dial-out events

Listen to these events to track the lifecycle of an outbound session:
call.on('dialout-connected', ({ sessionId, destination, sipCallId, provider }) => {
  // Call is established and ringing at the remote end
});

call.on('dialout-answered', ({ sessionId, destination, callerId, sipCallId }) => {
  // Remote party answered — media is now flowing
});

call.on('dialout-stopped', ({ sessionId, destination }) => {
  // Session ended (remote hung up or stopDialOut() was called)
});

call.on('dialout-error', ({ sessionId, errorMsg, details }) => {
  // Fatal error — session will not recover
  console.error(`Dial-out failed: ${errorMsg}`);
});

call.on('dialout-warning', ({ sessionId, errorMsg }) => {
  // Non-fatal warning (e.g. codec fallback applied)
});
dialout-connected fires when the SIP/PSTN network acknowledges the call; dialout-answered fires when the remote party actually picks up. Both events include provider and sipCallId for correlating with server-side logs. See the telephony events reference for full payload details.

Dial-in

Dial-in allows external SIP or PSTN callers to join a Daily room. Set enable_dialin: true on the room via the REST API or dashboard. For setup details — including PIN dial-in, pinless dial-in, and SIP interconnect — see the dial-in/dial-out guide.

Dial-in events

call.on('dialin-ready', ({ sipEndpoint, provider }) => {
  // The room's SIP endpoint is ready to accept inbound calls
  console.log('SIP endpoint:', sipEndpoint);
});

call.on('dialin-connected', ({ sessionId, sipFrom, sipCallId, provider }) => {
  // An inbound caller has connected and media negotiation is underway
  // Note: media is not yet flowing — wait for the participant-joined event
  console.log(`Caller ${sipFrom} connecting — session ${sessionId}`);
});

call.on('dialin-stopped', ({ sessionId, sipCallId }) => {
  // Inbound caller disconnected
});

call.on('dialin-error', ({ sessionId, errorMsg, type }) => {
  console.error(`Dial-in error [${type}]: ${errorMsg}`);
});

call.on('dialin-warning', ({ sessionId, errorMsg }) => {
  // Non-fatal warning (e.g. codec fallback)
});
All dial-in events include provider and sipCallId fields for correlating sessions with server-side logs. See the telephony events reference for full payload details.

Call transfers

To transfer an active SIP or PSTN session to another destination, use sipCallTransfer() or sipRefer(). See the call transfers guide for a full explanation of when to use each method, including a decision table and scenario examples.

Example: full dial-out flow

let activeSessionId = null;

async function callPhoneNumber(phoneNumber) {
  try {
    const { session } = await call.startDialOut({ phoneNumber });
    if (session) {
      activeSessionId = session.sessionId;
      updateUI({ status: 'ringing' });
    }
  } catch (err) {
    console.error('Failed to start dial-out:', err);
    updateUI({ status: 'error' });
  }
}

call.on('dialout-answered', ({ sessionId }) => {
  if (sessionId === activeSessionId) {
    updateUI({ status: 'connected' });
  }
});

call.on('dialout-stopped', ({ sessionId }) => {
  if (sessionId === activeSessionId) {
    activeSessionId = null;
    updateUI({ status: 'idle' });
  }
});

call.on('dialout-error', ({ sessionId, errorMsg }) => {
  if (sessionId === activeSessionId) {
    activeSessionId = null;
    updateUI({ status: 'error', message: errorMsg });
  }
});

async function endCall() {
  if (activeSessionId) {
    await call.stopDialOut({ sessionId: activeSessionId });
  }
}

async function pressKey(key) {
  if (activeSessionId) {
    await call.sendDTMF({ sessionId: activeSessionId, tones: key, method: 'auto' });
  }
}

See also