Handling asynchronous responses from the Artificial Intelligence API

Last updated: 2024-01-08Contributors
Edit this page

The Artificial Intelligence API operates in an asynchronous manner. Meaning the results of its operations are not returned in the HTTP response associated with the HTTP request performing the operation. Instead, developers specify a URL in their request, and when the operation completes, the response payload will be posted to the specified URL.

Th asynchronous nature of the Artificial Intelligence API requires developers to setup a simple web server in order to process results and run sample code found in this Developer Guide. Below, you will find simple web servers in a variety of languages to help you in this process.

Passing the webhook parameter in your requests

Asynchronous endpoints in the Artificial Intelligence API set require a query parameter named webhook. When you pass a URL to the API via this query parameter, the server will then post the results of the corresponding asynchronous API call to the provided URL when the operation completes. This will allow the server to process the operation in the background without tying up resources in the process.

The server will not validate the webhook URL. It's the developer responsibility to make sure that the webhook URL is valid and publicly accessible.

Correlating requests and responses

When processing files asynchronously, it is important to correlate every request with the proper response, as there is no guarantee in which order a response will be received. To help with this, a jobId is returned in the response body of every request, like so:

{"jobId":"a919924e-ce4e-11ed-xxxx-0050568c48bc"}

You should parse this response, and store the jobId associated with the media file being processed so that you can reliably associate the response that will be delivered in a webhook later to the file for which it pertains.

Job IDs expire after 1 week

The job ID returned in the response above only lasts for one week. To know the exact expiration time for the job, please use the GET jobs API below and look for the expirationTime.

Check on the status of your AI API job request

Upon submitting your AI API job request, a jobId is sent in the response as mentioned above. With this jobId, you can check on the progress of your AI API job request as follows:

GET /ai/status/v1/jobs/{jobId}

This will return a JSON response to show some details on your AI API job request:

{
    "jobId": "80800e1a-a663-11ee-b548-0050568ccd07",
    "api": "/ai/insights/v1/async/analyze-interaction",
    "creationTime": "2023-12-29T16:01:18.558Z",
    "expirationTime": "2024-01-05T16:01:18.558Z",
    "status": "InProgress"
}

Once the AI API job request is completed, using this API command will return the results of your AI API request.

{
    "jobId": "80800e1a-a663-11ee-b548-0050568ccd07",
    "api": "/ai/insights/v1/async/analyze-interaction",
    "creationTime": "2023-12-29T16:01:18.558Z",
    "completionTime": "2023-12-29T16:01:29.217Z",
    "expirationTime": "2024-01-05T16:01:18.558Z",
    "status": "Success",
    "response": {}
}

Working with asynchronous responses in development

Install and setup ngrok

If you are doing development on your local computer, or on a machine that is not publicly accessible on the Internet, then we recommend you download and install ngrok if you have not done so. Once installed, start your ngrok server and make note of its https URL. You will need to use this URL later when specifying the webhook callback address in Artificial Intelligence API requests.

$ ngrok http 3000
  Forwarding https://xxxx-73-170-11-87.ngrok-free.app -> http://localhost:3000

Create and start a simple web server

Let's create a simple web server on your local machine to receive webhook notifications when your Artificial Intelligence API file is processed and ready to be received.

Create a file called server.js using the contents below. Set the PORT to the same port number opening for the ngrok tunnel.

const http = require('http');
const fs = require('fs')
const PORT = 3000;

// Create a server to receive callback from RingCentral
const server = http.createServer( function(req, res) {
    if (req.method == 'POST' && req.url == "/webhook") {
        let body = []
        req.on('data', function(chunk) {
            body.push(chunk);
        }).on('end', function() {
            body = Buffer.concat(body).toString();
            jsonObj = JSON.parse(body)
            console.log(JSON.stringify(JSON.parse(body),null,4))
        });
        res.statusCode = 200
        res.end()
    } else {
      console.log(req.method, req.url)
      console.log("Unknown HTTP content received")
    }
});

// Start the server
try {
    server.listen(PORT);
} catch (e) {
    console.log("There was a problem starting the server: " + e)
}
console.log("Artificial Intelligence response server running at: https://localhost:" + PORT)

Finally, start your server.

$ node server.js

Create a file called server.py using the contents below. Set the PORT to the same PORT number opening for the ngrok tunnel.

from http.server import BaseHTTPRequestHandler, HTTPServer
from pathlib import Path
import os, json

HOSTNAME = "localhost"
PORT     = 3000

class S(BaseHTTPRequestHandler):
    def _set_response(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_POST(self):
        path = self.path
        if path == "/webhook":
            content_len = int(self.headers.get('Content-Length'))
            body = self.rfile.read(content_len)
            jsonObj = json.loads(body)
            print( json.dumps(jsonObj, indent=2, sort_keys=True))
            self._set_response()
        else:
            print ("Ignore this")


def run(server_class = HTTPServer, handler_class = S):
    server_address = (HOSTNAME, PORT)
    httpd = server_class(server_address, handler_class)
    print (f'Artificial Intelligence response server running at: https://{HOSTNAME}:{PORT}')
    httpd.serve_forever()

if __name__ == "__main__":
    from sys import argv

if len(argv) == 2:
    run(port=int(argv[1]))
else:
    run()

Finally, start your server.

$ python server.py

Create a folder named webhook and navigate to the new folder then create a file called server.php using the contents below.

<?php
if (isset($_REQUEST['webhook'])){
    $jsonStr = file_get_contents('php://input');
    $jsonObj = json_decode($jsonStr, TRUE);
    print_r ($jsonObj);
    file_put_contents("response.json", $jsonStr);
}else{
  echo ("Ignore this");
}
?>

Finally, start your server.

php -S localhost:3000

First, install the prerequisites.

$ pip install sinatra

Create and edit server.rb

Create a file called server.rb using the contents below. Set the PORT to the same PORT number opening for the ngrok tunnel.

require 'sinatra'
require 'json'

set :port, 3000
post '/webhook' do
    body = request.body.read
    jsonObj = JSON.parse(body)
    puts (jsonObj)
    status 200
    body 'OK'
end

Run your code

Finally, start your server.

$ ruby server.rb

We use .NET core which is cross-platform. You can get it here.

Create a Web server solution

mkdir server
cd server
dotnet new web
dotnet add package Newtonsoft.Json

Edit the Startup.cs file and override its content with code below:

using System;
using System.IO;
using System.Text;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;

namespace server
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment()) app.UseDeveloperExceptionPage();

            app.Run( async (context) =>
            {
                if (context.Request.Path == "/webhook" && context.Request.Method == "POST")
                {
                    using (StreamReader reader = new StreamReader(context.Request.Body, Encoding.UTF8))
                    {
                        var eventPayload = await reader.ReadToEndAsync();
                        dynamic jsonObj = JsonConvert.DeserializeObject(eventPayload);
                        Console.WriteLine("JobId: " + jsonObj.jobId);
                        Console.WriteLine("Status: " + jsonObj.status);
                        Console.WriteLine("API: " + jsonObj.api);
                        Console.WriteLine("creationTime: " + jsonObj.creationTime);
                        Console.WriteLine("completionTime: " + jsonObj.completionTime);
                        Console.WriteLine("expirationTime: " + jsonObj.expirationTime);
                        Console.WriteLine("==== RESPONSE ====");
                        Console.WriteLine(jsonObj.response);
                    }
                }
            });
        }
    }
}

Run the server code

The default port is 5000 and it is set in the launchSettings.json file under the server/Properties folder. Open the file and change the port number to match the opening port for the ngrok tunnel. E.g.

"applicationUrl": "https://localhost:3001;http://localhost:3000"

Finally, start your server. At the server terminal, run:

dotnet run

Create a WebhookServer project (using Eclipse IDE)

  • Create a new Java project
  • Select the Gradle Project wizard
  • Enter project name "AIServer"
  • Open the build.gradle file and add the dependencies to the project as shown below:
    dependencies {
        // ...
        implementation 'org.eclipse.jetty.aggregate:jetty-all:9.4.51.v20230217'
        implementation: 'javax.servlet:javax.servlet-api:4.0.1'
    }
    
  • Right-click the project in the Package Explorer and choose "Refresh Gradle Project" under the "Gradle" sub-menu

We use jetty-all version 9.4.x for our server. You can get a different version here if you want to.

Create a new Java Class

Select "File -> New -> Class" to create a new Java class named "WebhookServer"

Edit the WebhookServer.java with code below:

package AIServer;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;

public class WebhookServer extends AbstractHandler
{
    public static void main( String[] args ) throws Exception
    {
        Server server = new Server(3000);
        server.setHandler(new WebhookServer());
        server.start();
        server.join();
    }

    @Override
    public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
    {
        response.setStatus(HttpServletResponse.SC_OK);
        if (request.getMethod() == "POST" && request.getPathInfo().equals("/webhook"))
        {
            String body = request.getReader().lines().collect( java.util.stream.Collectors.joining(System.lineSeparator()) );
            System.out.println(body);
        }
        baseRequest.setHandled(true);
    }
}

Build and run the app from Eclipse.

With your web server up and running, and a way to route requests to it via ngrok, you are now ready to run code samples.