# 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 destination at once or in sequence.

## Forward to another number

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

<CodeTabs syncKey="lang">

```javascript title="Node.js"
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="+2341700000000">+2341800000001</Dial>
</Response>`;

  res.type("text/xml").send(oneml);
});

app.listen(3000);
```

```python title="Python"
from flask import Flask, request, Response

app = Flask(__name__)

@app.route("/voice/incoming", methods=["POST"])
def voice_incoming():
    oneml = """<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Connecting your call.</Say>
  <Dial callerId="+2341700000000">+2341800000001</Dial>
</Response>"""

    return Response(oneml, mimetype="text/xml")

if __name__ == "__main__":
    app.run(port=3000)
```

</CodeTabs>

With no `action` set, the call ends once the dialed party hangs up. PressOne has no more verbs to execute. The next two sections cover choosing the `callerId` and reacting to how the dial ends.

## Choose a caller ID

Set `callerId` to the number presented to the dialed party. It must be a number your account owns. If it is not, PressOne fails the dial outright, with `DialCallStatus` set to `failed` on the `action` request. When you omit `callerId`, PressOne presents the original caller's number instead, so the dialed party sees who originally called rather than a number from your account.

For a forward, set `callerId` to a number on your account that the recipient recognizes as belonging to your business, rather than passing through the original caller's number.

## Handle the outcome with action and DialCallStatus

Set `action` to have PressOne request your server once the dialed call ends, rather than letting the document end with no follow-up. The request carries `DialCallStatus`, one of `completed`, `busy`, `no-answer`, `canceled`, or `failed`. A common pattern falls back to a voicemail recording when the call does not complete:

<CodeTabs syncKey="lang">

```javascript title="Node.js"
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="+2341700000000" timeout="20">
    <Number>+2341800000001</Number>
  </Dial>
</Response>`;

  res.type("text/xml").send(oneml);
});

app.post("/voice/dial-complete", (req, res) => {
  const status = req.body.DialCallStatus;
  let oneml;

  if (status === "completed") {
    oneml = `<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Hangup/>
</Response>`;
  } else {
    oneml = `<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Our team is unavailable right now. Please 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,
  });

  const oneml = `<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Thank you. Your message has been recorded. Goodbye.</Say>
  <Hangup/>
</Response>`;

  res.type("text/xml").send(oneml);
});

app.listen(3000);
```

```python title="Python"
from flask import Flask, request, Response

app = Flask(__name__)

@app.route("/voice/incoming", methods=["POST"])
def voice_incoming():
    oneml = """<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Dial action="/voice/dial-complete" callerId="+2341700000000" timeout="20">
    <Number>+2341800000001</Number>
  </Dial>
</Response>"""

    return Response(oneml, mimetype="text/xml")

@app.route("/voice/dial-complete", methods=["POST"])
def dial_complete():
    status = request.form.get("DialCallStatus")

    if status == "completed":
        oneml = """<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Hangup/>
</Response>"""
    else:
        oneml = """<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Our team is unavailable right now. Please leave a message after the beep.</Say>
  <Record action="/voice/voicemail-complete" maxLength="120" finishOnKey="#"/>
  <Hangup/>
</Response>"""

    return Response(oneml, mimetype="text/xml")

@app.route("/voice/voicemail-complete", methods=["POST"])
def voicemail_complete():
    print("Voicemail received:", {
        "recordingSid": request.form.get("RecordingSid"),
        "recordingUrl": request.form.get("RecordingUrl"),
        "durationSeconds": request.form.get("RecordingDuration"),
    })

    oneml = """<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Thank you. Your message has been recorded. Goodbye.</Say>
  <Hangup/>
</Response>"""

    return Response(oneml, mimetype="text/xml")

if __name__ == "__main__":
    app.run(port=3000)
```

</CodeTabs>

`status` covers every non-`completed` outcome the same way: busy, unanswered, canceled, or failed all fall back to the voicemail branch. Treat `DialCallStatus` of `failed` as worth alerting on separately in production, since it can indicate a caller ID that is not on your account rather than a normal call outcome. See [recording calls](/voice/recording-calls) for more on handling the resulting `RecordingSid`, `RecordingUrl`, and `RecordingDuration`.

## Control when the caller's leg answers

`answerOnBridge` controls what the caller hears while PressOne places the dialed call. It defaults to `false`, which answers the caller's leg immediately, before dialing even starts. The caller typically hears silence rather than ringback until the call connects. Set it to `true` to delay answering the caller's leg until the dialed party picks up, so the caller hears actual ringing instead:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Dial callerId="+2341700000000" answerOnBridge="true">+2341800000001</Dial>
</Response>
```

## Ring multiple numbers: sequential vs parallel

`<Dial>` accepts more than one `<Number>` child to try more than one destination for the same call. By default, `sequential` is `false`, so PressOne rings every `<Number>` at once and bridges the caller to whichever one answers first:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Dial callerId="+2341700000000" action="/voice/dial-complete">
    <Number>+2341800000001</Number>
    <Number>+2341800000002</Number>
    <Number>+2341800000003</Number>
  </Dial>
</Response>
```

Set `sequential` to `true` to dial each `<Number>` one at a time instead, moving to the next only if the previous one does not complete (useful for an ordered escalation, such as a primary agent followed by a backup):

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Dial callerId="+2341700000000" action="/voice/dial-complete" sequential="true">
    <Number>+2341800000001</Number>
    <Number>+2341800000002</Number>
  </Dial>
</Response>
```

In both cases, `action` receives a single `DialCallStatus` for the overall attempt: `completed` once any target answers and the call ends. If none of the targets complete, PressOne reports a single failure status for the attempt; for sequential dialing this is always `no-answer`, regardless of why the last target specifically failed.

## Next steps

- [Dial](/voice/oneml/dial): the complete `<Dial>` and `<Number>` attribute reference.
- [Recording calls](/voice/recording-calls): record a voicemail message or log an entire call.
- [Twilio compatibility](/voice/twilio-compatibility): conference, client, SIP, and queue dial targets are not available; see the full matrix.
- [API reference](/api/voice): complete REST endpoint documentation.
