Signals Docs
Guides

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 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:

FieldRequiredDescription
nameYesA label, 1 to 120 characters.
voice_urlYesThe URL Signals requests when a call arrives. Must use https://, at most 2,048 characters, with no username, password or fragment.
voice_methodYesPOST or GET.
status_callback_url, status_callback_methodNoWhere Signals reports that a call ended. Set both or neither. See Status callbacks.
fallback_url, fallback_methodNoRequested once when the voice URL cannot be reached, times out or returns a document Signals cannot run. Set both or neither. See 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.

TerminalCode
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_:

JSONCode
{ "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 and the Programmable Voice API reference.

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 and the Numbers API reference. 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.

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);

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.

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);

A production menu dials an agent instead of hanging up. See Forwarding calls to connect the caller to another number.

Next steps

  • Webhooks: parameters, timeouts, fallback and call events for every request Signals sends your server.
  • Gather: every attribute for collecting digits and speech.
  • OneML overview: every verb available for scripting a call.
Last modified on