Build Warm Transfers with Twilio

Have you ever been disconnected from a support call while being transferred to someone else?

Warm transfer eliminates this problem. Using Twilio-powered warm transfers, your agents will have the ability add other people to an in-progress phone call to provide a seamless customer experience.

Here is how it works at a high level:

  1. The first agent becomes available when they connect through the web client.
  2. The second agent also becomes available when they connect through the web client.
  3. A client calls our support line.
  4. The client stays on hold while the first agent joins the call.
  5. While the first agent is on the phone with the client, the agent can dial a second agent into the call.
  6. Once the second agent is on the call, the first one can disconnect from it. The client and the second agent stay on the call.
Loading Code Samples...
Language
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\ActiveCall;
use App\Http\Controllers\Controller;
use Twilio\Twiml;
use Twilio\Rest\Client;

class ConferenceController extends Controller
{
    public function wait()
    {
        return $this->generateWaitTwiml();
    }

    public function connectClient(Request $request, Client $client)
    {
        $conferenceId = $request->input('CallSid');
        $twilioNumber = config('services.twilio')['number'];

        $this->createCall('agent1', $conferenceId, $client, $request);

        $activeCall = ActiveCall::firstOrNew(['agent_id' => 'agent1']);
        $activeCall->conference_id = $conferenceId;
        $activeCall->save();

        return $this->generateConferenceTwiml($conferenceId, false, true, '/conference/wait');
    }

    public function connectAgent1($conferenceId)
    {
        return $this->generateConferenceTwiml($conferenceId, true, false);
    }

    public function connectAgent2($conferenceId)
    {
        return $this->generateConferenceTwiml($conferenceId, true, true);
    }

    public function callAgent2($agentId, Request $request, Client $client)
    {
        $twilioNumber = config('services.twilio')['number'];
        $conferenceId = ActiveCall::where('agent_id', $agentId)->first()->conference_id;

        return $this->createCall('agent2', $conferenceId, $client, $request);
    }

    private function createCall($agentId, $conferenceId, $client, $request)
    {
        $destinationNumber = 'client:' . $agentId;
        $twilioNumber = config('services.twilio')['number'];
        $path = str_replace($request->path(), '', $request->url()) . 'conference/connect/' . $conferenceId . '/' . $agentId;
        try {
            $client->calls->create(
                'client:' . $agentId, // The agent_id that will receive the call
                $twilioNumber, // The number of the phone initiating the call
                [
                    'url' => $path // The URL Twilio will request when the call is answered
                ]
            );
        } catch (Exception $e) {
            return 'Error: ' . $e->getMessage();
        }
        return 'ok';
    }

    private function generateConferenceTwiml($conferenceId, $startOnEnter, $endOnExit, $waitUrl = null)
    {
        if ($waitUrl === null){
            $waitUrl = 'http://twimlets.com/holdmusic?Bucket=com.twilio.music.classical';
        }
        $response = new Twiml();
        $dial = $response->dial();
        $dial->conference(
            $conferenceId,
            ['startConferenceOnEnter' => $startOnEnter,
            'endConferenceOnExit' => $endOnExit,
            'waitUrl' => $waitUrl]
        );
        return response($response)->header('Content-Type', 'application/xml');
    }

    private function generateWaitTwiml(){
        $response = new Twiml();
        $response->say(
            'Thank you for calling. Please wait in line for a few seconds. An agent will be with you shortly.',
            ['voice' => 'alice', 'language' => 'en-GB']
        );
        $response->play('http://com.twilio.music.classical.s3.amazonaws.com/BusyStrings.mp3');
        return response($response)->header('Content-Type', 'application/xml');
    }
}
app/Http/Controllers/ConferenceController.php
Create a conference call, put the client on hold, and dial agent #1

app/Http/Controllers/ConferenceController.php

Find your language and framework of choice below to get to the source code in your language and follow step-by-step instructions to learn how to build warm transfers yourself:

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

Loading Code Samples...
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\ActiveCall;
use App\Http\Controllers\Controller;
use Twilio\Twiml;
use Twilio\Rest\Client;

class ConferenceController extends Controller
{
    public function wait()
    {
        return $this->generateWaitTwiml();
    }

    public function connectClient(Request $request, Client $client)
    {
        $conferenceId = $request->input('CallSid');
        $twilioNumber = config('services.twilio')['number'];

        $this->createCall('agent1', $conferenceId, $client, $request);

        $activeCall = ActiveCall::firstOrNew(['agent_id' => 'agent1']);
        $activeCall->conference_id = $conferenceId;
        $activeCall->save();

        return $this->generateConferenceTwiml($conferenceId, false, true, '/conference/wait');
    }

    public function connectAgent1($conferenceId)
    {
        return $this->generateConferenceTwiml($conferenceId, true, false);
    }

    public function connectAgent2($conferenceId)
    {
        return $this->generateConferenceTwiml($conferenceId, true, true);
    }

    public function callAgent2($agentId, Request $request, Client $client)
    {
        $twilioNumber = config('services.twilio')['number'];
        $conferenceId = ActiveCall::where('agent_id', $agentId)->first()->conference_id;

        return $this->createCall('agent2', $conferenceId, $client, $request);
    }

    private function createCall($agentId, $conferenceId, $client, $request)
    {
        $destinationNumber = 'client:' . $agentId;
        $twilioNumber = config('services.twilio')['number'];
        $path = str_replace($request->path(), '', $request->url()) . 'conference/connect/' . $conferenceId . '/' . $agentId;
        try {
            $client->calls->create(
                'client:' . $agentId, // The agent_id that will receive the call
                $twilioNumber, // The number of the phone initiating the call
                [
                    'url' => $path // The URL Twilio will request when the call is answered
                ]
            );
        } catch (Exception $e) {
            return 'Error: ' . $e->getMessage();
        }
        return 'ok';
    }

    private function generateConferenceTwiml($conferenceId, $startOnEnter, $endOnExit, $waitUrl = null)
    {
        if ($waitUrl === null){
            $waitUrl = 'http://twimlets.com/holdmusic?Bucket=com.twilio.music.classical';
        }
        $response = new Twiml();
        $dial = $response->dial();
        $dial->conference(
            $conferenceId,
            ['startConferenceOnEnter' => $startOnEnter,
            'endConferenceOnExit' => $endOnExit,
            'waitUrl' => $waitUrl]
        );
        return response($response)->header('Content-Type', 'application/xml');
    }

    private function generateWaitTwiml(){
        $response = new Twiml();
        $response->say(
            'Thank you for calling. Please wait in line for a few seconds. An agent will be with you shortly.',
            ['voice' => 'alice', 'language' => 'en-GB']
        );
        $response->play('http://com.twilio.music.classical.s3.amazonaws.com/BusyStrings.mp3');
        return response($response)->header('Content-Type', 'application/xml');
    }
}