Skip to main content

Weather Phone IVR - Node.js

This guide is using Relay V4

This guide is available for Relay V3 and for Relay V4. You are currently viewing the guide for Relay V4. To switch to the older Relay V3 guide, please use the selection field below:

Overview

This code sample is a simple weather phone IVR application that uses the SignalWire Realtime API to provide current weather report to the caller in Washington DC either as a phone call or text.

In particular, this sample demonstrates how you can use the SignalWire realtime API to easily accomplish the following:

  • Making a phone call programmatically using your numbers
  • Waiting for, and programmatically receiving, phone calls to your numbers
  • Taking user input from their dial pad to select between actions
  • Connecting to a different phone call
  • Sending a text message to the caller
  • Playing audio, Text-to-Speech and ringtones through the phone

The code we'll write is waiting for you at the other end.

Required Resources

To be able to run this example, you will need a SignalWire account which you can create here. From your SignalWire Space, you need your SignalWire API credentials. If you need help finding these credentials, visit Navigating your SignalWire Space.

Further, you'll need to get a number from SignalWire which will be used as the number for the IVR. You can get that from your SignalWire Dashboard. You can follow Buying a Phone Number if you need help.

If you are planning to make or allow calls outside of the US from SignalWire numbers, please note that you'll have to manually enable it. Follow [Enabling International Outbound Voice & SMS](Enabling International Outbound Voice & SMS) guide for more information.

To be able to run this project, you need to have Node.js and Node Package Manager (npm) installed.

This sample code is available on GitHub here. You can follow the README.md instructions to download and run the sample.

How to Run the Application

  1. Edit the settings of your phone number so that our code can handle it. The Relay Context you specify here needs to match the one you specify in the code later. In this case, we have used 'office'.
A screenshot of the Edit Settings page of a phone number in SignalWire. The number is set to accept incoming calls as Voice Calls, handle the calls using a Relay Application, and forward the call to a relay context called 'office'.
  1. Start by adding your Project ID and API credentials, and the phone number you bought to the env.sample file, in the format specified.
  2. Rename the env.sample file as .env.
  3. Run yarn install in the directory to download the required libraries.
  4. Run node index.js to start the program. If all went well, it should now be listening for phone calls to the number specified in .env file.

Alternatively, if you want to use docker to run a container. Instead of doing yarn install, do the following instead:

  1. docker build . -t weather-ivr to build the container using the dockerfile already in the repo.
  2. docker run -d weather-ivr to run it.

Code

The main code for this project lives in a single file: index.js, and from there we'll make and receive the phone call. Other than that, there are a few more files that we will briefly discuss:

  1. .env.sample file contains the format in which you will provide your SignalWire API credentials to the code. Copy this file to .env and provide your credentials. Please take care to not commit your .env file on GitHub, or make it public in any way.

  2. package.json lists out the dependencies that you will need to run the code. This includes the SignalWire Realtime SDK. When you run npm install or yarn install, the packages listed here will be downloaded and installed.

View the related GitHub Repository to explore this codebase.

Abridged code sample

const DC_WEATHER_PHONE = "+12025891212";
const PHONE_NUMBER = process.env.PHONE_NUMBER;
const REALTIME_CONFIG = {
project: process.env.PROJECT_ID,
token: process.env.API_TOKEN,
contexts: ["office"],
};
const { Voice, Messaging } = require("@signalwire/realtime-api");
const messageClient = new Messaging.Client(REALTIME_CONFIG);
const voiceClient = new Voice.Client(REALTIME_CONFIG);

// How to call a number and play a song there.
async function callWithRainDance(number) {
try {
let call = await voiceClient.dialPhone({
from: PHONE_NUMBER,
to: number,
timeout: 30,
});
const rainDance = await call.playAudio({
url: "https://swrooms.com/rain.mp3",
});
await rainDance.ended();
await call.hangup();
} catch (e) {
console.log("Call not answered.", e);
}
}

// How to wait for calls and how to answer them
voiceClient.on("call.received", async (call) => {
console.log("Got call from", call.from, "to number", call.to);
await call.answer();

let welcomeSpeech = await call.playTTS({
text: "Hello! Welcome to Knee Rub's Weather Helpline.",
gender: "male",
});
await welcomeSpeech.ended();

const cmdPrompt = await call.promptTTS({
text: `Please enter
1 for Washington weather,
2 for washington weather message,
3 to play rain dance,
4 to send rain dance`,
digits: {
max: 1,
digitTimeout: 15,
},
});
let { digits } = await cmdPrompt.ended();

switch (digits) {
case "1": // We are going to dial a washington weather
// number and connect the call.
await call.connectPhone({
from: call.from,
to: DC_WEATHER_PHONE,
ringback: new Voice.Playlist().add(
Voice.Playlist.TTS({
text: "ring. ring. ring. ring. ring. ring. ring. ring",
})
),
});
await call.waitUntilConnected();
break;

case "2": // We are going to query a weather API, find the weather of
// Washington, and message that weather to the user's number.
let weather = await getWeatherFromOpenWeatherMap("Washington DC");
let message = `Washington weather: ${
weather.weather[0].description
}. Temperature: ${(weather.main.temp - 273).toFixed(2)}°C`;

try {
await messageClient.send({
from: PHONE_NUMBER,
to: call.to,
body: message,
});
} catch (e) {
let pb = await call.playTTS({
text: "Sorry, Couldn't send the message.",
});
await pb.ended();
}
break;

case "3": // We are going to play a rain dance song hosted on our servers.
const rainDance = await call.playAudio({
url: "https://swrooms.com/rain.mp3",
});
await rainDance.ended();
break;

case "4": // We are going to ask for a phone number to dial and play a
// rain dance song to it.
const prompt = await call.promptTTS({
text: "Please enter your friend's number then dial #.",
digits: {
max: 15,
digitTimeout: 15,
terminators: "#",
},
});
const { digits } = await prompt.ended();

if (ise164("+" + digits)) {
callWithRainDance("+" + digits);
} else {
let pb = await call.playTTS({
text: "The number is invalid. Bye.",
});
await pb.ended();
}
}
await call.hangup(); // hang up
});

As evident from the source code above, SignalWire Voice API makes it incredibly easy to write complex IVRs. You can get pretty far by using the SignalWire Realtime SDK reference and this code as a starting point.

In case there's some confusion, here's a brief walkthrough anyway:

Installing the libraries

Download the required SignalWire Realtime Libraries by using npm install @signalwire/realtime-api@~3. After you've done this, you can import required namespaces into your project using the following line:

const { Voice, Messaging } = require("@signalwire/realtime-api");

Instantiating Voice and Messaging client

Using your Project ID and API token, you can instantiate both Voice and Messaging Client.

contexts parameter: Relay context allows you to treat a bunch of phone numbers as one, so that you can subscribe to and receive events (calls, messages etc) from your group of numbers at once. You can pick which numbers are in which Relay context from the SignalWire Dashboard. For this example, I've already set up my number to be in the office context (read Buying a Phone Number if you're unfamiliar with the process).

const realtimeConfig = {
project: process.env.PROJECT_ID,
token: process.env.API_TOKEN,
contexts: ["office"],
};

const messageClient = new Messaging.Client(realtimeConfig);
const voiceClient = new Voice.Client(realtimeConfig);

Waiting for calls

Once you've successfully instantiated your Realtime Clients, you can use them to register events pertaining to the context you've selected (like waiting for calls and messages to the numbers). In the example below, notice how easy it is to register event listener for incoming calls.

console.log("Waiting for calls...");
voiceClient.on("call.received", async (call) => {
console.log("Got call from", call.from, "to number", call.to);
await call.answer();
console.log("Inbound call answered");
});

With this simple bit of code, as long as this program is running, the phone call to your number will automatically be received by this code.

Of course, picking up the call is but the first step. Now you'll have to interact with the user. Simply use the Call object that is passed to the event listener as parameter to perform actions and listen for user input. Like maybe:

  1. making a robot say hello with call.playTTS
  2. taking in user input with call.prompt
  3. connecting the call to another phone call (like the Washington Weather Phone Number) with connectPhone or connectSip

And more.

Making calls

Making calls is even simpler. Use the Voice Client's dialPhone and dialSip methods to make calls. In fact, if these are too simple for you, feel free to use the more general purpose dial method with Device Builder to make series and parallel calls at once.

// Call someone and play them a nice song.

let call = await voiceClient.dialPhone({
from: PHONE_NUMBER,
to: "{NUMBER TO CALL IN E.164 FORMAT}",
timeout: 30,
});
const rainDance = await call.playAudio({
url: "https://cdn.signalwire.com/swml/April_Kisses.mp3",
});
await rainDance.ended();
await call.hangup();

Wrap up

The GitHub Repo

SignalWire RealTime SDK Reference

Sign Up Here

If you would like to test this example out, you can create a SignalWire account and space.

Please feel free to reach out to us on our Community Slack or create a Support ticket from your SignalWire Space if you need guidance!