Disconnect or reject an incoming call in Twilio

All the call related operations such as call accept, reject, disconnect, mute and so on, must be performed on the Call object. You use the call.disconnect() function to disconnect a call and the call.reject() function to reject a call.

Call disconnection can happen in any of the following three ways:
  • Agent rejects the incoming call (before the call is accepted).
  • Agent disconnects the call once the conversation is complete.
  • Customer disconnects the call.

Scenario 1: Agent rejects the incoming call (before the call is accepted)

The following flow diagram shows the sequence of operations performed once an agent rejects the call from Fusion or from the media toolbar application:

The reject incoming call scenario in Twilio.

  1. An agent can reject a call either from the Fusion application or from your media toolbar application. When agent clicks on the Decline button in the Fusion application, the onToolbarInteractionCommand event is fired with the command of reject.
  2. The partner application receives this event and if the event is to reject the call, a reject call to the Twilio API is made.
  3. When the CTI supplier notifies the partner application that the call is rejected, the partner application can fire the closeCommEvent action with reason as REJECT.
  4. When the Fusion application identifies this action, the call dialog is discarded from the UI.

During the incoming event, the Twilio.Call object is included in the event payload. You can use the call.reject() function to reject a call as shown in the following example:

public async rejectCall(): Promise<void> {
        if (this.call) {
            this.call.reject();
        }
    }

Scenario 2: Agent disconnects the call once the conversation is complete

The following flow diagram shows the sequence of operations performed once an agent disconnects the call from Fusion or from the media toolbar application:

The disconnect call scenario in Twilio.

  1. An agent can reject a call either from the Fusion application or from your media toolbar application. When agent clicks on the Decline button in the Fusion application, the onToolbarInteractionCommand event is fired with the command of disconnect.
  2. The partner application receives this event and if the event is to disconnect the call, a reject call to the Twilio API is made.
  3. When the CTI supplier notifies the partner application that the call is rejected, the partner application can fire the closeCommEvent action with reason as HANGUP.
  4. When the Fusion application identifies this action, it renders the wrap up window in the UI.

You can add the disconnect and reject event listeners in the call object received in the incoming event handler as shown in the following example:

public incomingCallCallback = (call: Call) => {
    this.integrationEventsHandler.incomingCallHandler(call.parameters.From, call.parameters.CallSid);
    this.call = call;
    this.call.on("cancel", () => { this.integrationEventsHandler.callRejectedHandler(call.parameters.CallSid) });
    this.call.on("disconnect", () => { this.integrationEventsHandler.callHangupHandler(call.parameters.CallSid) });
    this.call.on("reject", () => { this.integrationEventsHandler.callRejectedHandler(call.parameters.CallSid) });
}

Scenario 3: Customer disconnects the call

The following flow diagram shows the sequence of operations performed when the customer disconnects the call:

Customer disconnects the call scenario.

  1. When a customer disconnects a call, Twilio fires the disconnected event.
  2. You need to add an event listener in your media toolbar application for the reject and disconnect events
  3. The closeCommEvent action is fired from your media toolbar application with reason as REJECT or WRAPUP.
  4. When the Fusion application identifies this action, it renders the wrap up window in the UI.

You can add the disconnect and reject event listeners in the call object received in the incoming event handler as shown in the following example:

public incomingCallCallback = (call: Call) => {
    this.integrationEventsHandler.incomingCallHandler(call.parameters.From, call.parameters.CallSid);
    this.call = call;
    this.call.on("cancel", () => { this.integrationEventsHandler.callRejectedHandler(call.parameters.CallSid) });
    this.call.on("disconnect", () => { this.integrationEventsHandler.callHangupHandler(call.parameters.CallSid) });
    this.call.on("reject", () => { this.integrationEventsHandler.callRejectedHandler(call.parameters.CallSid) });
}

Complete code

Here's the complete code for the vendorHandler.ts file for disconnecting a call.

import { Call, Device } from '@twilio/voice-sdk';
import { ICtiVendorHandler } from './ICtiVendorHandler';
import { IntegrationEventsHandler } from '../integrationEventsHandler';
 
export class VendorHandler implements ICtiVendorHandler {
    private twilio: any;
    private device: Device | null;
    private integrationEventsHandler: IntegrationEventsHandler;
    private call: Call | null;
    public idAndToken: any;
 
    constructor(integrationEventsHandler: IntegrationEventsHandler) {
        this.twilio = (window as any).Twilio;
        this.device = null;
        this.idAndToken = null;
        this.integrationEventsHandler = integrationEventsHandler;
        this.call = null;
    }
 
    public async makeAgentAvailable(): Promise<void> {
        this.idAndToken = await this.getIdAndToken();
        this.device = new this.twilio.Device(this.idAndToken.token, {
            logLevel: 1,
            codecPreferences: ["opus", "pcmu"],
            enableRingingState: true
        });
        let resolve: Function;
        let reject: Function;
        if (this.device) {
            this.device.on("registered", () => {
                console.log("Registration completed ...")
                resolve();
            });
            this.device.on("error", (deviceError) => {
                console.error("Registration Failed ...", deviceError);
                reject();
            });
 
            this.device.on("incoming", this.incomingCallCallback);
 
            this.device.register();
        }
        return new Promise((res: Function, rej: Function) => {
            resolve = res;
            reject = rej;
        });
    }
 
    public incomingCallCallback = (call: Call) => {
        this.integrationEventsHandler.incomingCallHandler(call.parameters.From, call.parameters.CallSid);
        this.call = call;
        this.call.on("cancel", () => { this.integrationEventsHandler.callRejectedHandler(call.parameters.CallSid) });
        this.call.on("disconnect", () => { this.integrationEventsHandler.callHangupHandler(call.parameters.CallSid) });
        this.call.on("reject", () => { this.integrationEventsHandler.callRejectedHandler(call.parameters.CallSid) });
    }
    public async makeAgentUnavailable(): Promise<void> {
        let resolve: Function;
        let reject: Function;
        if (this.device) {
            this.device.on("unregister", () => {
                console.log("Successfully UnRegistered ...")
                resolve();
            });
            this.device.on("error", (deviceError) => {
                console.error("Failed to unregister ...", deviceError);
                reject();
            });
            this.device.unregister();
        }
        return new Promise((res: Function, rej: Function) => {
            resolve = res;
            reject = rej;
        });
    }
    public async makeOutboundCall(phoneNumber: string, eventId: string): Promise<void> {
        throw new Error('Method not implemented.');
    }
    public async acceptCall(): Promise<void> {
        if (this.call) {
            this.call.accept();
        }
    }
    public async rejectCall(): Promise<void> {
        if (this.call) {
            this.call.reject();
        }
    }
    public async hangupCall(): Promise<void> {
        if (this.call) {
            this.call.disconnect();
        }
    }
 
    private async getIdAndToken(): Promise<any> {
        const headers: Headers = (new Headers()) as Headers;
        headers.set('Accept', 'application/json');
        const request: Request = new Request('https://twilio-voice-stream.com/token', {
            method: 'GET',
            headers: headers
        }) as Request;
        const idAndToken: Response = await fetch(request);
        this.idAndToken = await idAndToken.json();
        return this.idAndToken;
    }
}

Verify your progress

After finishing these steps, use OJET server to start you application and sign in to your Fusion application. Open the media toolbar and make your agent available for calls by clicking on the Agent Availability button. Then, start a call to your customer care number. You'll receive the incoming call notification in your media toolbar application and in your Fusion application window. You can accept the call from your media toolbar application or from your Fusion application or from your softphone application. Once you accept the call, your media toolbar state will change to the ACCEPTED state and the engagement is opened in your Fusion application. You can disconnect the call from your media toolbar application or from your Fusion application or from the softphone. Once you disconnect the call, your media toolbar state will be changed to the DISCONNECTED state.