Redirect inbound telephony calls with Call Automation

Important

This feature of Azure Communication Services is currently in preview.

Preview APIs and SDKs are provided without a service-level agreement. We recommend that you don't use them for production workloads. Some features might not be supported, or they might have constrained capabilities.

For more information, review Supplemental Terms of Use for Microsoft Azure Previews.

Get started with Azure Communication Services by using the Call Automation SDKs to build automated calling workflows that listen for and manage inbound calls placed to a phone number or received via ACS direct routing.

Prerequisites

Create a new C# application

In the console window of your operating system, use the dotnet command to create a new web application with the name 'IncomingCallRedirect':

 dotnet new web -n IncomingCallRedirect 

Install the NuGet package

During the preview phase, the NuGet package can be obtained by configuring your package manager to use the Azure SDK Dev Feed

Install the NuGet packages: Azure.Communication.CallAutomation and Azure.Messaging.EventGrid to your project.

dotnet add <path-to-project> package Azure.Communication.CallAutomation --prerelease
dotnet add <path-to-project> package Azure.Messaging.EventGrid --prerelease

Use Visual Studio Dev Tunnels for your Event Grid subscription

In this quick-start, you'll use the new Visual Studio Dev Tunnels feature to obtain a public domain name so that your local application is reachable by the Call Automation platform on the Internet. The public name is needed to receive the Event Grid IncomingCall event and Call Automation events using webhooks.

If you haven't already configured your workstation, be sure to follow the steps in this guide. Once configured, your workstation will acquire a public domain name automatically allowing us to use the environment variable ["VS_TUNNEL_URL"] as shown below.

Set up your Event Grid subscription to receive the IncomingCall event by reading this guide.

Configure Program.cs to redirect the call

Using the minimal API feature in .NET 6, we can easily add an HTTP POST map and redirect the call.

In this code snippet, /api/incomingCall is the default route that will be used to listen for incoming calls. At a later step, you'll register this url with Event Grid. Since Event Grid requires you to prove ownership of your Webhook endpoint before it starts delivering events to that endpoint, the code sample also handles this one time validation by processing SubscriptionValidationEvent. This requirement prevents a malicious user from flooding your endpoint with events. For more information, see this guide.

using Azure.Communication;
using Azure.Communication.CallAutomation;
using Azure.Messaging.EventGrid;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json;

var builder = WebApplication.CreateBuilder(args);

var client = new CallAutomationClient("<resource_connection_string_obtained_in_pre-requisites>");

Console.WriteLine($"Tunnel URL:{builder.Configuration["VS_TUNNEL_URL"]}"); // echo Tunnel URL to screen to configure Event Grid webhook

var app = builder.Build();

app.MapPost("/api/incomingCall", async (
    [FromBody] EventGridEvent[] eventGridEvents) =>
    {
        foreach (var eventGridEvent in eventGridEvents)
        {
            if (eventGridEvent.TryGetSystemEventData(out object eventData))
            {
                // Handle the subscription validation event.
                if (eventData is SubscriptionValidationEventData subscriptionValidationEventData)
                {
                    var responseData = new SubscriptionValidationResponse
                    {
                        ValidationResponse = subscriptionValidationEventData.ValidationCode
                    };
                    return Results.Ok(responseData);
                }
            }
            
            var jsonObject = JsonNode.Parse(eventGridEvent.Data).AsObject();
            var incomingCallContext = (string)jsonObject["incomingCallContext"];
            await client.RedirectCallAsync(incomingCallContext, new PhoneNumberIdentifier("<phone_number_to_redirect_call_to")); //this can be any phone number you have access to and should be provided in format +(countrycode)(phonenumber)
        }

        return Results.Ok();
    });

app.Run();

Update the placeholders in the code above for connection string and phone number to redirect to.

Run the app

Open Your_Project_Name.csproj file in your project with Visual Studio, and then select Run button or press F5 on your keyboard.

Prerequisites

Create a new Java Spring application

Configure the Spring Initializr to create a new Java Spring application.

  1. Set the Project to be a Maven Project.
  2. Leave the rest as default unless you want to have your own customization.
  3. Add Spring Web to Dependencies section.
  4. Generate the application and it will be downloaded as a zip file. Unzip the file and start coding.

Install the Maven package

Configure Azure Artifacts development feed:

Since the Call Automation SDK version used in this QuickStart isn't yet available in Maven Central Repository, we need to configure an Azure Artifacts development feed, which contains the latest version of the Call Automation SDK.

Follow the instruction here for adding azure-sdk-for-java feed to your POM file.

Add Call Automation package references:

azure-communication-callautomation - Azure Communication Services Call Automation SDK package is retrieved from the Azure SDK Dev Feed configured above.

Look for the recently published version from here

And then add it to your POM file like this (using version 1.0.0-alpha.20221101.1 as example)

<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-communication-callautomation</artifactId>
<version>1.0.0-alpha.20221101.1</version>
</dependency>

Add other packages’ references:

azure-messaging-eventgrid - Azure Event Grid SDK package: com.azure : azure-messaging-eventgrid. Data types from this package are used to handle Call Automation IncomingCall event received from the Event Grid.

<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-messaging-eventgrid</artifactId>
    <version>4.11.2</version>
</dependency>

gson - Google Gson package: com.google.code.gson : gson is a serialization/deserialization library to handle conversion between Java Objects and JSON.

<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.9.0</version>
</dependency>

Set up a public URI for the local application

In this quick-start, you'll use Ngrok tool to project a public URI to the local port so that your local application can be visited by the internet. The public URI is needed to receive the Event Grid IncomingCall event and Call Automation events using webhooks.

First, determine the port of your java application. 8080 is the default endpoint of a spring boot application.

Then, install Ngrok and run Ngrok with the following command: ngrok http <port>. This command will create a public URI like https://ff2f-75-155-253-232.ngrok.io/, and it is your Ngrok Fully Qualified Domain Name(Ngrok_FQDN). Keep Ngrok running while following the rest of this quick-start.

Redirect incoming call

In your project folder, create a Controller.java file and update it to handle incoming calls.

In this code snippet, /api/incomingCall is the default route that will be used to listen for incoming calls. At a later step, we'll register this url with Event Grid. Since Event Grid requires you to prove ownership of your Webhook endpoint before it starts delivering events to that endpoint, the code sample also handles this one time validation by processing SubscriptionValidationEvent. This requirement prevents a malicious user from flooding your endpoint with events. For more information, see this guide.

package com.example.demo;

import com.azure.communication.callautomation.*;
import com.azure.communication.callautomation.models.*;
import com.azure.communication.callautomation.models.events.*;
import com.azure.communication.common.CommunicationIdentifier;
import com.azure.communication.common.PhoneNumberIdentifier;
import com.azure.messaging.eventgrid.EventGridEvent;
import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationEventData;
import com.azure.messaging.eventgrid.systemevents.SubscriptionValidationResponse;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.time.Duration;
import java.util.*;
import static org.springframework.web.bind.annotation.RequestMethod.POST;

@RestController
public class ActionController {
    private CallAutomationAsyncClient client;
    private String connectionString = "<resource_connection_string>"; //noted from pre-requisite step    
    
private CallAutomationAsyncClient getCallAutomationAsyncClient() {
        if (client == null) {
            client = new CallAutomationClientBuilder()
                .connectionString(connectionString)
                .buildAsyncClient();
        }
        return client;
    }
    @RequestMapping(value = "/api/incomingCall", method = POST)
    public ResponseEntity<?> handleIncomingCall(@RequestBody(required = false) String requestBody) {
        List<EventGridEvent> eventGridEvents = EventGridEvent.fromString(requestBody);
        for (EventGridEvent eventGridEvent : eventGridEvents) {
            // Handle the subscription validation event            
            if (eventGridEvent.getEventType().equals("Microsoft.EventGrid.SubscriptionValidationEvent")) {
                SubscriptionValidationEventData subscriptionValidationEventData = eventGridEvent.getData().toObject(SubscriptionValidationEventData.class);
                SubscriptionValidationResponse subscriptionValidationResponse = new SubscriptionValidationResponse()
                        .setValidationResponse(subscriptionValidationEventData.getValidationCode());
                ResponseEntity<SubscriptionValidationResponse> ret = new ResponseEntity<>(subscriptionValidationResponse, HttpStatus.OK);
                return ret;
            }
          JsonObject data = new Gson().fromJson(eventGridEvent.getData().toString(), JsonObject.class);
          String incomingCallContext = data.get("incomingCallContext").getAsString();
          CommunicationIdentifier target = new PhoneNumberIdentifier("<phone_number_to_redirect_to>");
          RedirectCallOptions redirectCallOptions = new RedirectCallOptions(incomingCallContext, target); 
          Response<Void> response = getCallAutomationAsyncClient().redirectCallWithResponse(redirectCallOptions).block();                               
        }
        
        return new ResponseEntity<>(HttpStatus.OK);
    }
}

Update the placeholders in the code above for connection string and phone number to redirect to.

Run the app

To run your Java application, run maven compile, package, and execute commands.

mvn compile
mvn package
mvn exec:java -Dexec.mainClass=com.example.demo.DemoApplication -Dexec.cleanupDaemonThreads=false

Subscribe to IncomingCall event

IncomingCall is an Azure Event Grid event for notifying incoming calls to your Communication Services resource. To learn more about it, see this guide.

  1. Navigate to your resource on Azure portal and select Events from the left side menu.

  2. Select + Event Subscription to create a new subscription.

  3. Filter for Incoming Call event.

  4. Choose endpoint type as web hook and provide the public url generated for your application by ngrok. Make sure to provide the exact api route that you programmed to receive the event previously. In this case, it would be <ngrok_url>/api/incomingCall.

    Screenshot of portal page to create a new event subscription.

    If your application does not send 200Ok back to Event Grid in time, Event Grid will use exponential backoff retry to send the incoming call event again. However, an incoming call only rings for 30 seconds, and acting on a call after that will not work. To avoid retries after a call expires, we recommend setting the retry policy in the Additional Features tab as: Max Event Delivery Attempts to 2 and Event Time to Live to 1 minute. Learn more about retries here.

  5. Select create to start the creation of subscription and validation of your endpoint as mentioned previously. The subscription is ready when the provisioning status is marked as succeeded.

This subscription currently has no filters and hence all incoming calls will be sent to your application. To filter for specific phone number or a communication user, use the Filters tab.

Testing the application

  1. Place a call to the number you acquired in the Azure portal (see prerequisites above).
  2. Your Event Grid subscription to the IncomingCall should execute and call your application.
  3. The call will be redirected to the endpoint(s) you specified in your application.

Since this call flow involves a redirected call instead of answering it, pre-call web hook callbacks to notify your application the other endpoint accepted the call aren't published.

Clean up resources

If you want to clean up and remove a Communication Services subscription, you can delete the resource or resource group. Deleting the resource group also deletes any other resources associated with it. Learn more about cleaning up resources.

Next steps