Switch to conference-based forwarding with agent features
Replaced problematic Number URL approach with conference-based forwarding to eliminate the "call cannot be completed" issue. Key improvements: - Forward calls now use Conference instead of direct Dial with URL - Caller is placed in conference with hold music while waiting for agent - Agent receives outbound call to join conference with proper caller ID - Agent hears "Incoming call from XXX XXX XXXX" announcement - Conference-based architecture enables future DTMF features - Proper call flow without TwiML interference Technical details: - Added conference status monitoring webhooks - Agent call includes proper caller announcement - Conference starts when agent joins, ends when caller leaves - Hold music plays while waiting for agent - Eliminated URL attribute on Number elements that caused audio issues - Added Conference element support in append_twiml_element function This resolves the voicemail and "call cannot be completed" issues while maintaining call forwarding functionality and preparing for advanced agent features. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -217,6 +217,27 @@ class TWP_Webhooks {
|
||||
'callback' => array($this, 'handle_agent_action'),
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
// Conference status webhook
|
||||
register_rest_route('twilio-webhook/v1', '/conference-status', array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array($this, 'handle_conference_status'),
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
// Agent conference join webhook
|
||||
register_rest_route('twilio-webhook/v1', '/agent-conference-join', array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array($this, 'handle_agent_conference_join'),
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
// Agent call status webhook (for conference calls)
|
||||
register_rest_route('twilio-webhook/v1', '/agent-call-status-new', array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array($this, 'handle_agent_call_status_new'),
|
||||
'permission_callback' => '__return_true'
|
||||
));
|
||||
|
||||
// Request callback webhook
|
||||
register_rest_route('twilio-webhook/v1', '/request-callback', array(
|
||||
@@ -2842,4 +2863,129 @@ class TWP_Webhooks {
|
||||
|
||||
return $this->send_twiml_response($response->asXML());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle conference status events
|
||||
*/
|
||||
public function handle_conference_status($request) {
|
||||
$params = $request->get_params();
|
||||
|
||||
error_log('TWP Conference Status: ' . print_r($params, true));
|
||||
|
||||
$status_callback_event = isset($params['StatusCallbackEvent']) ? $params['StatusCallbackEvent'] : '';
|
||||
$conference_sid = isset($params['ConferenceSid']) ? $params['ConferenceSid'] : '';
|
||||
$friendly_name = isset($params['FriendlyName']) ? $params['FriendlyName'] : '';
|
||||
|
||||
// Log conference events for debugging
|
||||
switch ($status_callback_event) {
|
||||
case 'conference-start':
|
||||
error_log('TWP Conference: Conference started: ' . $friendly_name);
|
||||
break;
|
||||
case 'participant-join':
|
||||
error_log('TWP Conference: Participant joined: ' . $friendly_name);
|
||||
break;
|
||||
case 'participant-leave':
|
||||
error_log('TWP Conference: Participant left: ' . $friendly_name);
|
||||
break;
|
||||
case 'conference-end':
|
||||
error_log('TWP Conference: Conference ended: ' . $friendly_name);
|
||||
break;
|
||||
}
|
||||
|
||||
return new WP_REST_Response('OK', 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle agent joining conference with features
|
||||
*/
|
||||
public function handle_agent_conference_join($request) {
|
||||
$params = $request->get_params();
|
||||
|
||||
error_log('TWP Agent Conference Join: ' . print_r($params, true));
|
||||
|
||||
$conference_name = isset($_GET['conference_name']) ? $_GET['conference_name'] : '';
|
||||
$caller_number = isset($_GET['caller_number']) ? $_GET['caller_number'] : '';
|
||||
|
||||
$response = new \Twilio\TwiML\VoiceResponse();
|
||||
|
||||
if (empty($conference_name)) {
|
||||
$response->say('Conference not found', ['voice' => 'alice']);
|
||||
$response->hangup();
|
||||
return $this->send_twiml_response($response->asXML());
|
||||
}
|
||||
|
||||
// Announce the incoming call to the agent
|
||||
if (!empty($caller_number)) {
|
||||
$response->say('Incoming call from ' . $this->format_phone_number_for_speech($caller_number), ['voice' => 'alice']);
|
||||
} else {
|
||||
$response->say('Incoming call', ['voice' => 'alice']);
|
||||
}
|
||||
|
||||
// Set up agent conference with features
|
||||
$dial = $response->dial();
|
||||
$conference = $dial->conference($conference_name, [
|
||||
'startConferenceOnEnter' => true, // Start when agent joins
|
||||
'endConferenceOnExit' => false, // Don't end when agent leaves (let caller stay)
|
||||
'muted' => false,
|
||||
'beep' => false,
|
||||
'waitUrl' => '',
|
||||
// Enable DTMF detection for agent features
|
||||
'eventCallbackUrl' => home_url('/wp-json/twilio-webhook/v1/conference-events'),
|
||||
'record' => false // We'll control recording via DTMF
|
||||
]);
|
||||
|
||||
error_log('TWP Agent Conference Join: Joining agent to conference: ' . $conference_name);
|
||||
|
||||
return $this->send_twiml_response($response->asXML());
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle agent call status for conference calls
|
||||
*/
|
||||
public function handle_agent_call_status_new($request) {
|
||||
$params = $request->get_params();
|
||||
|
||||
error_log('TWP Agent Call Status: ' . print_r($params, true));
|
||||
|
||||
$call_status = isset($params['CallStatus']) ? $params['CallStatus'] : '';
|
||||
$call_sid = isset($params['CallSid']) ? $params['CallSid'] : '';
|
||||
|
||||
switch ($call_status) {
|
||||
case 'initiated':
|
||||
error_log('TWP Agent Call: Call initiated to agent: ' . $call_sid);
|
||||
break;
|
||||
case 'ringing':
|
||||
error_log('TWP Agent Call: Agent phone ringing: ' . $call_sid);
|
||||
break;
|
||||
case 'answered':
|
||||
error_log('TWP Agent Call: Agent answered: ' . $call_sid);
|
||||
break;
|
||||
case 'completed':
|
||||
error_log('TWP Agent Call: Agent call completed: ' . $call_sid);
|
||||
break;
|
||||
case 'busy':
|
||||
case 'no-answer':
|
||||
case 'failed':
|
||||
error_log('TWP Agent Call: Agent call failed (' . $call_status . '): ' . $call_sid);
|
||||
// TODO: Could implement fallback logic here (try next agent, voicemail, etc.)
|
||||
break;
|
||||
}
|
||||
|
||||
return new WP_REST_Response('OK', 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format phone number for speech (adds pauses between digits)
|
||||
*/
|
||||
private function format_phone_number_for_speech($number) {
|
||||
// Remove +1 country code and format for speech
|
||||
$cleaned = preg_replace('/^\+1/', '', $number);
|
||||
|
||||
if (strlen($cleaned) == 10) {
|
||||
// Format as (xxx) xxx-xxxx with pauses
|
||||
return substr($cleaned, 0, 3) . ' ' . substr($cleaned, 3, 3) . ' ' . substr($cleaned, 6, 4);
|
||||
}
|
||||
|
||||
return $number;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user