guides
Call Events

Call Events Integration Guide

Overview

This guide explains how to integrate with JAS Connect's webhook system to monitor call events, determine participants and call direction, and use call recordings after completion.

You will:

  1. Use call_status events to track when a call status events during the call.
  2. Use new_recording events to detect when a call ends.
  3. Make further requests to get the recording and other information.

Webhook Event Types

JAS Connect emits two main webhook events:

Event NameDescriptionTiming
call_statusIndicates a call is in progressDuring the call
call_analysisIndicates a new ai analysis has been initiatedAfter transcription completes
new_recordingIndicates a new recording has been createdAfter the call ends

Event Handling Workflow

1. Processing call_status Events

Payload Example

{
    "eventName": "call_status",
    "timestamp": "2025-04-14T15:04:23.161Z",
    "data": {
        "event": {
            "entity": "/callcontrol/101/participants/172",
            "event_type": 0
        },
        "sequence": 518
    }
}

The event_type will be 0 if the participant is inserted, 1 if the participant is removed.

Steps

  1. Extract the extension and participant ID:

    From the entity string:

    /callcontrol/{dn}/participants/{participant_id}

    For example:

    const entity = "/callcontrol/101/participants/172"
    const parts = entity.split("/")
    const dn = parts[2] // "101"
    const participantId = parts[4] // "172"
    if (event.event_type === 0) {
        // fetch call status
    } else {
        // participant is removed (call leg ended)
    }
  2. Fetch participant details:

    Call the participant info API to get call metadata:

    GET /api/callcontrol/{dn}/participants/{participantId}

    Response example:

    {
        "id": 36,
        "status": "Dialing",
        "dn": "101",
        "party_caller_name": "",
        "party_dn": "10000",
        "party_caller_id": "5148888888",
        "party_did": "",
        "device_id": "sip:101@127.0.0.1:5063",
        "party_dn_type": "Wexternalline",
        "direct_control": false,
        "originated_by_dn": "",
        "originated_by_type": "None",
        "referred_by_dn": "",
        "referred_by_type": "None",
        "on_behalf_of_dn": "",
        "on_behalf_of_type": "None",
        "callid": 26,
        "legid": 1
    }
  3. Interpret the data:

The callid attribute uniquely identifies the call across call_status events. The id attribute uniquely identifies the call participant accross call_status events.

  1. Store active call:
const dnCalls = activeCalls.get(dn)
dnCalls.set(participantId, status)

2. Processing new_recording Events

Payload Example

{
    "eventName": "new_recording",
    "timestamp": "2025-04-14T15:04:23.161Z",
    "data": {
        "call_id": "26", // from call control
        "start_time": "2025-04-14T15:04:23.161Z",
        "end_time": "2025-04-14T15:04:23.161Z",
        "recording_url": "101/[John Doe]_101-5148888888_20250603153720(26).wav",
        "id_recording": 1,
        "cdr_id": "61f450de-8b46-4326-a306-32eb6ad5e534"
    }
}

Fetch the Recording

GET /api/recordings/:url
import requests
 
recording_url = "101/[John Doe]_101-5148888888_20250603153720(26).wav"
url = f"https://api.jasconnect.com/v2/api/recordings/{recording_url}"
headers = {
    "Authorization": "Bearer TOKEN"
}
 
response = requests.get(url, headers=headers)
response.raise_for_status()
 
with open("example.mp3", "wb") as f:
    f.write(response.content)

3. Processing call_analysis Events

The call_analysis event is sent when AI analysis is initiated for a call. This event is sent after transcription completes and tracks the analysis generation process.

Event Flow

The call_analysis event can be sent with three different status values:

  • started: Sent after transcription completes, before summary generation. Both summary and keypoints will be empty (summary: "", keypoints: []).
  • done: Sent when analysis is complete with status "Completed". Includes actual summary and keypoints if available.
  • failed: Sent if analysis fails. Both summary and keypoints will be empty (summary: "", keypoints: []).

Payload Example

{
    "eventName": "call_analysis",
    "timestamp": "2025-04-14T15:04:23.161Z",
    "data": {
        "direction": "inbound",
        "dn": "101",
        "analysis_link": "https://api.jasconnect.com/v2/api/analysis/123",
        "status": "done",
        "recording_url": "101/[John Doe]_101-5148888888_20250603153720(26).wav",
        "call_id": "26",
        "start_time": "2025-04-14T15:00:00.000Z",
        "end_time": "2025-04-14T15:05:00.000Z",
        "summary": "Customer called to inquire about product availability. Discussed pricing and delivery options.",
        "keypoints": [
            "Product inquiry",
            "Pricing discussion",
            "Delivery timeline"
        ]
    }
}

Payload Structure

FieldTypeDescription
directionstringCall direction: "inbound" or "outbound"
dnstringDirectory number
analysis_linkstringLink to call analysis
statusstringAnalysis status: "started", "done", or "failed"
recording_urlstringURL path to the call recording
call_idstringUnique identifier for the call
start_timestringISO 8601 timestamp when the call began
end_timestringISO 8601 timestamp when the call ended
summarystringCall summary text (always present, empty string if not available)
keypointsstring[]Array of key points from the call (always present, empty array if not available)

Important: Both summary and keypoints are always present in the payload, even if empty. When status is "started" or "failed", they will be empty (summary: "", keypoints: []).


Example Application

This project is a simple HTTP server in Python that handles incoming call_status and new_recording events via a Jas Connect Webhooks. The server registers itself with the Jas Connect API to receive call status updates and logs relevant call information upon receiving those updates.

Prerequisites

  • Python 3.x
  • Required Python packages: requests

You can install the required dependencies using:

pip install requests

Overview

The server does the following:

  1. Registers itself to receive call_status events from the Jas Connect API.
  2. Listens for incoming HTTP POST requests that contain call status updates.
  3. Upon receiving an update, logs relevant call details by calling the call control API endpoint that provides information about the call participants.
  4. Unregisters the webhook when the server is stopped.

Configuration

You need to replace the following variables with your specific configuration:

  • TOKEN: The API token for authenticating requests (recieved from /auth/authenticate).
  • CALLBACK_URL: The public URL where the webhook will send events (An easy way test this is using ngrok).
TOKEN = "your_token"
CALLBACK_URL = "your_callback_url"

How to Run

  1. Update the configuration at the top of the script with your values.
  2. Run the Python script:
python main.py
  1. The server will start listening on the specified port (9999 by default) and will register for call_status events with the Jas Connect API.

  2. Example application output

Successfully registered for call status event
Serving on http://0.0.0.0:9999
Recieved event #256 call_status at 2025-03-27T22:02:01.758811044-04:00
Status Dialing | 103 <-> 5148888888
Recieved event #257 call_status at 2025-03-27T22:02:01.770012536-04:00
Status Dialing | 103 <-> 5148888888
Recieved event #259 call_status at 2025-03-27T22:02:07.60556135-04:00
Status Connected | 103 <-> 5148888888
Recieved event #260 call_status at 2025-03-27T22:02:07.608772016-04:00
Status Connected | 103 <-> 5148888888
Recieved event #258 call_status at 2025-03-27T22:02:07.605558344-04:00
Status Connected | 103 <-> 5148888888
Recieved event #261 call_status at 2025-03-27T22:02:11.192685568-04:00
Recieved event #262 call_status at 2025-03-27T22:02:11.197793332-04:00
^CSuccessfully unregistered call status event

Usage

  • When the server receives a call_status event, it will parse the event, extract call information, and log it by making a GET request to the Jas Connect API.
  • The server will continue running and listening for events until manually stopped.

Application

import json
from http.server import BaseHTTPRequestHandler, HTTPServer
import hmac
import hashlib
 
import requests
 
TOKEN = ""
CALLBACK_URL = ""
WEBHOOK_SECRET = ""
PORT = 9999
HOST = "0.0.0.0"
 
JAS_CONNECT = "https://api.jasconnect.com/v2"
 
active_calls: dict[str, dict[str, dict]] = {}
 
 
def register_call_status(url: str):
    headers = {
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json",
    }
 
    for event in ["call_status", "new_recording"]:
        data = {
            "event": event,
            "url": url,
        }
 
        response = requests.post(f"{JAS_CONNECT}/webhooks", json=data, headers=headers)
        if response.status_code == 200:
            print(f"Successfully registered for {event} event")
        else:
            print(
                f"Failed to register for events: {response.status_code}, {response.text}"
            )
            exit(1)
 
 
def unregister_call_status():
    headers = {
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json",
    }
 
    for event in ["call_status", "new_recording"]:
        response = requests.delete(
            f"{JAS_CONNECT}/webhooks?event={event}", json=None, headers=headers
        )
 
        if response.status_code == 202:
            print(f"Successfully unregistered {event} event")
 
 
def handle_call_status(data, timestamp):
    sequence = data["data"]["sequence"]
    entity: str = data["data"]["event"]["entity"]
    parts = entity.split("/")
    dn, id = parts[2], parts[4]
    print(f"Recieved event #{sequence} call_status at {timestamp}")
    if data["data"]["event"]["event_type"] > 0:
        del active_calls[dn][id]
        print(f"Call {id} ended")
        return
 
    headers = {
        "Authorization": f"Bearer {TOKEN}",
        "Content-Type": "application/json",
    }
    response = requests.get(
        f"{JAS_CONNECT}/api/callcontrol/{dn}/participants/{id}",
        json=None,
        headers=headers,
    )
    if response.status_code != 200:
        if response.status_code != 403:
            print(f"Recieved status {response.status_code} from call control api")
            exit(1)
        return
 
    data = response.json()
 
    if not active_calls.get(dn):
        active_calls[dn] = {}
 
    active_calls[dn][id] = data
 
 
def handle_new_recording(data, timestamp):
    print(
        f"Recieved event for dn {data['data']['dn']}, call #{data['data']['call_id']} new_recording at {timestamp} url: {data['data']['recording_url']}"
    )
    # updated last call with dn and call_id
    pass
 
 
def verify_webhook(secret: str, message: str, received_signature: str) -> bool:
    expected_signature = hmac.new(
        secret.encode(), message.encode(), hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected_signature, received_signature)
 
 
class CallHandler(BaseHTTPRequestHandler):
    def log_message(self, format: str, *args) -> None:
        pass
 
    def do_POST(self):
        content_length = int(self.headers.get("Content-Length", 0))
        body = self.rfile.read(content_length).decode("utf-8")
        data: dict = json.loads(body)
 
        sig = self.headers.get("X-SIG")
        if not sig:
            print("No signature found in request")
            return
        if not verify_webhook(WEBHOOK_SECRET, body, sig):
            print("Webhook signature is invalid")
            return
 
        timestamp = data["timestamp"]
        event_name = data["eventName"]
        if event_name == "call_status":
            handle_call_status(data, timestamp)
            self.send_response(200)
        elif event_name == "new_recording":
            handle_new_recording(data, timestamp)
            self.send_response(200)
        print(active_calls)
        print()
 
 
def main():
    unregister_call_status()
    register_call_status(CALLBACK_URL)
    try:
        with HTTPServer((HOST, PORT), CallHandler) as server:
            print(f"Serving on http://{HOST}:{PORT}")
            server.serve_forever()
    except KeyboardInterrupt:
        unregister_call_status()
 
 
if __name__ == "__main__":
    main()