# Receiving calls

This guide walks you through answering inbound calls: you create a voice application that points at your server, route a number to it, and return OneML that greets the caller and offers a menu.

## How inbound calls reach your server

A *voice application* holds the URLs on your server that Signals uses for a call. A *number route* points a number your project owns at that application. When someone dials the number:

1. Signals finds the voice application the number is routed to.
2. Signals requests the application's voice URL, sending parameters such as `CallSid`, `From` and `To`. See [Webhooks](/programmable-voice/webhooks) for the full set.
3. Your server returns a OneML document, and Signals runs it on the call.

One application can serve many numbers. Changing the application's voice URL changes it for every number routed to it.

## Create a voice application

A voice application has these fields:

| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | A label, 1 to 120 characters. |
| `voice_url` | Yes | The URL Signals requests when a call arrives. Must use `https://`, at most 2,048 characters, with no username, password or fragment. |
| `voice_method` | Yes | `POST` or `GET`. |
| `status_callback_url`, `status_callback_method` | No | Where Signals reports that a call ended. Set both or neither. See [Status callbacks](/programmable-voice/status-callbacks). |
| `fallback_url`, `fallback_method` | No | Requested once when the voice URL cannot be reached, times out or returns a document Signals cannot run. Set both or neither. See [Fallback URL](/programmable-voice/webhooks#fallback-url). |

### In the console

1. Switch the console to **Programmable Voice** and open **Applications**.
2. Create an application, enter its name and voice URL, and choose the method.

### With the Signals API

Send a `POST` to `/v1/voice/applications` with an API key and an `Idempotency-Key`. Repeating the request with the same key within 24 hours returns the same application instead of creating a second one.

```bash
curl -X POST "https://api.signals.pressone.africa/v1/voice/applications" \
  -H "Authorization: Bearer $SIGNALS_API_KEY" \
  -H "Idempotency-Key: 3f9d2c7e-ivr-production" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Customer support IVR",
    "voice_url": "https://voice.example.com/voice/incoming",
    "voice_method": "POST",
    "fallback_url": "https://backup.example.com/voice/fallback",
    "fallback_method": "POST"
  }'
```

A `201` response returns the application. Note its `id`, which starts with `vap_`:

```json
{
  "id": "vap_0192b3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b",
  "project_id": "prj_0192b3c4-0000-7000-8000-000000000001",
  "name": "Customer support IVR",
  "voice_url": "https://voice.example.com/voice/incoming",
  "voice_method": "POST",
  "status_callback_url": null,
  "status_callback_method": null,
  "fallback_url": "https://backup.example.com/voice/fallback",
  "fallback_method": "POST",
  "numbers_pointed": 0,
  "revision": 1,
  "created_at": "2026-07-02T12:00:00Z",
  "updated_at": "2026-07-02T12:00:00Z"
}
```

The application is live as soon as the API returns it. If your organization has more than one project, add an `X-Project-ID` header naming the project. See [Authentication](/getting-started/authentication) and the [Programmable Voice API reference](/api/programmable-voice).

## Route a number to the application

1. In the console, open **Numbers** and select the number or block.
2. Open **Routes**, choose the numbers to route, and pick your voice application as the target.
3. Save. The route shows as pending until the voice network applies it, then as applied.

To route numbers through the API, see [Numbers and routing](/sip-trunks/numbers-and-routing) and the [Numbers API reference](/api/numbers). A number routed to a voice application stops reaching any SIP trunk it was routed to before.

## Answer with a greeting

The following server answers every inbound call with a spoken greeting. It is complete and runnable. Signals only requests `https://` URLs, so put it behind HTTPS (a tunnel works for testing) and set the application's voice URL to `https://YOUR_HOST/voice/incoming`.

<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>Thank you for calling Acme Delivery. Have a good day.</Say>
  <Hangup/>
</Response>`;

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

app.listen(3000);
```

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

app = Flask(__name__)

@app.route("/voice/incoming", methods=["POST"])
def voice_incoming():
    oneml = """<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Thank you for calling Acme Delivery. Have a good day.</Say>
  <Hangup/>
</Response>"""

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

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

</CodeTabs>

:::note
Serve responses with a `Content-Type` of `text/xml`. `res.type("text/xml")` and `Response(..., mimetype="text/xml")` set it in the examples above.
:::

## Build a two-option menu

Extend the same server into a menu that branches on caller input. The voice URL route returns a `<Gather>` that collects one digit and sends it to an `action` route, which returns a different document for each option.

<CodeTabs syncKey="lang">

```javascript title="Node.js"
const express = require("express");
const app = express();
app.use(express.urlencoded({ extended: false }));

const reply = (res, body) =>
  res.type("text/xml").send(`<?xml version="1.0" encoding="UTF-8"?>
<Response>
${body}
</Response>`);

app.post("/voice/incoming", (req, res) => {
  reply(
    res,
    `  <Gather action="/voice/menu" numDigits="1" timeout="10">
    <Say>For sales, press 1. For support, press 2.</Say>
  </Gather>
  <Say>We did not receive your selection. Goodbye.</Say>
  <Hangup/>`,
  );
});

app.post("/voice/menu", (req, res) => {
  const choices = {
    1: "Connecting you to sales.",
    2: "Connecting you to support.",
  };
  const message = choices[req.body.Digits] || "That is not a valid option. Goodbye.";

  reply(res, `  <Say>${message}</Say>\n  <Hangup/>`);
});

app.listen(3000);
```

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

app = Flask(__name__)


def reply(body):
    oneml = f"""<?xml version="1.0" encoding="UTF-8"?>
<Response>
{body}
</Response>"""
    return Response(oneml, mimetype="text/xml")


@app.route("/voice/incoming", methods=["POST"])
def voice_incoming():
    return reply("""  <Gather action="/voice/menu" numDigits="1" timeout="10">
    <Say>For sales, press 1. For support, press 2.</Say>
  </Gather>
  <Say>We did not receive your selection. Goodbye.</Say>
  <Hangup/>""")


@app.route("/voice/menu", methods=["POST"])
def voice_menu():
    choices = {
        "1": "Connecting you to sales.",
        "2": "Connecting you to support.",
    }
    message = choices.get(request.form.get("Digits"), "That is not a valid option. Goodbye.")
    return reply(f"  <Say>{message}</Say>\n  <Hangup/>")


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

</CodeTabs>

A production menu dials an agent instead of hanging up. See [Forwarding calls](/programmable-voice/forwarding-calls) to connect the caller to another number.

## Next steps

- [Webhooks](/programmable-voice/webhooks): parameters, timeouts, fallback and call events for every request Signals sends your server.
- [Gather](/programmable-voice/oneml/gather): every attribute for collecting digits and speech.
- [OneML overview](/programmable-voice/oneml/overview): every verb available for scripting a call.
