Signals Docs
Guides

Forwarding calls

This guide covers connecting a caller to another phone number with the <Dial> verb: choosing a caller ID, reacting to how the dial ends, and ringing more than one number at once or in order.

Forward to another number

<Dial> bridges the current call to a phone number, given either as text content or as one or more <Number> children. The following server answers an inbound call and forwards it:

const express = require("express"); const app = express(); app.use(express.urlencoded({ extended: false })); app.post("/voice/incoming", (req, res) => { const oneml = `<?xml version="1.0" encoding="UTF-8"?> <Response> <Say>Connecting your call.</Say> <Dial callerId="+2342013300100">+2348051234567</Dial> </Response>`; res.type("text/xml").send(oneml); }); app.listen(3000);

With no action set, the call ends once the dialed party hangs up, because there are no more verbs to run. The next sections cover the caller ID and how to react to the outcome.

Choose a caller ID

Set callerId to the number the dialed party sees. It must be a number your Signals project owns. If it is not, the dial fails, with DialCallStatus set to failed on the action request.

When you omit callerId, Signals uses To for inbound calls and From for calls created through the Calls API. The selected number must be eligible and owned by your project. Set callerId when you need to present a different owned number. An unowned or ineligible number makes the dial fail.

Dialed numbers go out through your project's outbound policy and limits, the same as calls from your SIP trunks. See Outbound policies.

Handle the outcome with action and DialCallStatus

Set action to have Signals request your server once the dialed call ends. The request carries DialCallStatus, one of completed, busy, no-answer, canceled or failed. A common pattern falls back to voicemail when the call does not complete:

const express = require("express"); const app = express(); app.use(express.urlencoded({ extended: false })); app.post("/voice/incoming", (req, res) => { const oneml = `<?xml version="1.0" encoding="UTF-8"?> <Response> <Dial action="/voice/dial-complete" callerId="+2342013300100" timeout="20"> <Number>+2348051234567</Number> </Dial> </Response>`; res.type("text/xml").send(oneml); }); app.post("/voice/dial-complete", (req, res) => { const status = req.body.DialCallStatus; if (status === "failed") { console.warn("Dial failed for call", req.body.CallSid); } const oneml = status === "completed" ? `<?xml version="1.0" encoding="UTF-8"?> <Response> <Hangup/> </Response>` : `<?xml version="1.0" encoding="UTF-8"?> <Response> <Say>Our team is unavailable right now. Leave a message after the beep.</Say> <Record action="/voice/voicemail-complete" maxLength="120" finishOnKey="#"/> <Hangup/> </Response>`; res.type("text/xml").send(oneml); }); app.post("/voice/voicemail-complete", (req, res) => { console.log("Voicemail received:", { recordingSid: req.body.RecordingSid, recordingUrl: req.body.RecordingUrl, durationSeconds: req.body.RecordingDuration, }); res.type("text/xml").send(`<?xml version="1.0" encoding="UTF-8"?> <Response> <Say>Thank you. Your message has been recorded. Goodbye.</Say> <Hangup/> </Response>`); }); app.listen(3000);

Every outcome other than completed falls back to voicemail. The handler also logs failed separately, because a failed dial often means a configuration problem, such as a caller ID your project does not own or a destination your outbound policy blocks, rather than a caller who did not answer. See Recording calls for handling RecordingSid, RecordingUrl and RecordingDuration.

Control when the caller's leg answers

answerOnBridge controls what the caller hears while Signals places the dialed call. It defaults to false, which answers the caller's leg before dialing starts, so the caller usually hears silence until the call connects. Set it to true to leave the caller's leg unanswered until the dialed party picks up, so the caller hears ringing instead:

XMLCode
<?xml version="1.0" encoding="UTF-8"?> <Response> <Dial callerId="+2342013300100" answerOnBridge="true">+2348051234567</Dial> </Response>

Ring multiple numbers in parallel or in order

<Dial> accepts more than one <Number> child. By default, sequential is false, so Signals rings every number at once and bridges the caller to whichever answers first:

XMLCode
<?xml version="1.0" encoding="UTF-8"?> <Response> <Dial callerId="+2342013300100" action="/voice/dial-complete"> <Number>+2348051234567</Number> <Number>+2348051234568</Number> <Number>+2348051234569</Number> </Dial> </Response>

Set sequential to true to dial each number in turn, moving to the next only if the previous one does not complete. Use it for an ordered escalation, such as a primary agent followed by a backup:

XMLCode
<?xml version="1.0" encoding="UTF-8"?> <Response> <Dial callerId="+2342013300100" action="/voice/dial-complete" sequential="true"> <Number>+2348051234567</Number> <Number>+2348051234568</Number> </Dial> </Response>

In both cases, action receives one DialCallStatus for the whole attempt: completed once any number answers and the call ends. If none of the numbers complete, Signals reports a single failure status. For sequential dialing this is always no-answer, whatever the reason the last number failed.

Next steps

Last modified on