Handling Incoming Phone Calls with Server Side Swift, Vapor and Twilio

June 01, 2018
Written by
Sam Agnew
Twilion

Screen Shot 2018-06-01 at 11.37.46 AM

You’re building a Vapor app and want to be able to handle phone calls? Let’s walk through how to add Twilio Programmable Voice to the barebones Vapor “Hello World” app.

Swift Package Manager

For this project we’re going to use Swift Package Manager to set everything up and install dependencies. Make sure you have Swift 4.0 or greater installed before moving on. You can check your Swift version by running the following in your terminal:

swift --version

Vapor has a convenient command line tool which can be used to generate templates for projects. Installing this will also take care of other necessary dependencies.

You can install it using Homebrew with the following command:

brew tap vapor/homebrew-tap
brew install vapor

Now initiate a new Swift project with the following terminal command in the directory where you want your code to live:

swift package init --type executable

This will generate a directory structure for your Swift project. By default it will name your project after the directory you ran the previous command in. My project was named Hello, so you may need to replace that in any of the commands or code further in this post that refer to your Swift project.

Next let’s get our dependencies set up. Open the file Package.swift and add the Vapor package to the code Swift Package Manager generated. My Package.swift looks like this:

import PackageDescription

let package = Package(
    name: "Hello",
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(url: "https://github.com/vapor/vapor.git", from: "3.0.2")
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "Hello",
            dependencies: ["Vapor"]),
    ]
)

Getting started with Vapor

We’re now ready to move on to writing some Swift code. From the base directory of your project, open the file Sources/Hello/main.swift and add the following code to it:

import Vapor

let app = try Application()
let router = try app.make(Router.self)

router.get("/") { req in
    return "Hello, world."
}

try app.run()

This is the bare bones “Hello World” Vapor application. It has one route that takes a GET request and responds with a simple string. With your Package.swift configured and your Vapor code written, run the following commands in the terminal to build and run the project:

swift build
./.build/debug/Hello

This will install Vapor and all of its dependencies and will run your web application. Once it is running, visit http://localhost:8080/ to see a basic “Hello World” webpage.

Setting up your Twilio account

Before being able to handle calls, you’ll need a Twilio phone number. You get one free phone number with your trial account.

Your Vapor app will need to be visible from the Internet in order for Twilio to send requests to it whenever it receives a call. We will use ngrok for this, which you’ll need to install if you don’t have it. In your terminal run the following command:

ngrok http 8080

If you’ve just installed ngrok and that previous command didn’t work, you might have to run it like ./ngrok http 8080 from the directory that the ngrok executable is in.

This provides us with a publicly accessible URL to the Vapor application. Configure your phone number as seen in this image by adding your ngrok URL with /call appended to it to the “Voice & Fax” section where it says “A call comes in”:

You are now ready to receive a phone call to your new Twilio number.

Adding Twilio Programmable Voice to your Vapor app

Now whenever someone calls your Twilio number, Twilio will send a POST request to the /call route on your Vapor app. Let’s write some code to handle that request.

Let’s add this route to the app. Replace all of the code in main.swift with the following:

import Vapor

let app = try Application()
let router = try app.make(Router.self)

router.post("call")  { req -> HTTPResponse in
    // Declare a multi-line string literal of some TwiML to respond with.
    let twiml = """
    <?xml version="1.0" encoding="UTF-8"?>
    <Response>
        <Play>http://demo.twilio.com/docs/classic.mp3</Play>
    </Response>
    """

    // Create an HTTP Response with the twiml as the body.
    var response = HTTPResponse(status: .ok, body: twiml)
    response.contentType = .xml
    return response
}

try app.run()

In the new /call route we’re creating a string for the TwiML that we are going to respond to Twilio’s request with, and then putting it in an HTTPResponse object with a content type of XML. The Play tag in the XML string tells Twilio to play a sound file over the phone call. In this example, I used a URL that we commonly use for demos, but you can replace it with a URL to any sound file.

Build and run your server code again and try calling your Twilio number! You might recognize the song.

What just happened?

With Vapor running on port 8080, sitting behind a public ngrok URL, Twilio can see your application. Upon receiving a phone call:

  1. Twilio will send a POST request to /call.
  2. The function associated with the call route will be executed.
  3. This function responds to Twilio’s request with TwiML telling Twilio to play a sound file over the phone call.

If you want to do more with Swift and Twilio, you can check out these other posts:

If you have any questions or other cool ideas, feel free to reach out and let me know: