diff --git a/admin/class-twp-admin.php b/admin/class-twp-admin.php
index f2b32f0..ef6f093 100644
--- a/admin/class-twp-admin.php
+++ b/admin/class-twp-admin.php
@@ -481,6 +481,60 @@ class TWP_Admin {
Default Twilio phone number to use as sender for SMS messages when not in a workflow context.
+
+
+
+
+ Discord & Slack Notifications
+ Configure webhook URLs to receive call notifications in Discord and/or Slack channels.
+ |
+
+
+
+ Discord Webhook URL |
+
+
+ Discord webhook URL for call notifications. How to create a Discord webhook
+ |
+
+
+
+ Slack Webhook URL |
+
+
+ Slack webhook URL for call notifications. How to create a Slack webhook
+ |
+
+
+
+ Notification Settings |
+
+
+ Choose which events trigger Discord/Slack notifications.
+ |
+
+
+
+ Queue Timeout Threshold |
+
+
+ seconds
+ Send notification if call stays in queue longer than this time (30-1800 seconds).
+ |
+
@@ -2725,6 +2779,14 @@ class TWP_Admin {
register_setting('twilio-wp-settings-group', 'twp_urgent_keywords');
register_setting('twilio-wp-settings-group', 'twp_sms_notification_number');
register_setting('twilio-wp-settings-group', 'twp_default_sms_number');
+
+ // Discord/Slack notification settings
+ register_setting('twilio-wp-settings-group', 'twp_discord_webhook_url');
+ register_setting('twilio-wp-settings-group', 'twp_slack_webhook_url');
+ register_setting('twilio-wp-settings-group', 'twp_notify_on_incoming_calls');
+ register_setting('twilio-wp-settings-group', 'twp_notify_on_queue_timeout');
+ register_setting('twilio-wp-settings-group', 'twp_notify_on_missed_calls');
+ register_setting('twilio-wp-settings-group', 'twp_queue_timeout_threshold');
}
/**
diff --git a/includes/class-twp-activator.php b/includes/class-twp-activator.php
index 7a533d6..e44e3b0 100644
--- a/includes/class-twp-activator.php
+++ b/includes/class-twp-activator.php
@@ -366,6 +366,12 @@ class TWP_Activator {
if (empty($status_index_exists)) {
$wpdb->query("ALTER TABLE $table_queued_calls ADD INDEX status (status)");
}
+
+ // Add notified_timeout column for Discord/Slack notifications
+ $notified_timeout_exists = $wpdb->get_results("SHOW COLUMNS FROM $table_queued_calls LIKE 'notified_timeout'");
+ if (empty($notified_timeout_exists)) {
+ $wpdb->query("ALTER TABLE $table_queued_calls ADD COLUMN notified_timeout datetime AFTER agent_call_sid");
+ }
}
/**
diff --git a/includes/class-twp-call-queue.php b/includes/class-twp-call-queue.php
index 9e020e2..0ea9e44 100644
--- a/includes/class-twp-call-queue.php
+++ b/includes/class-twp-call-queue.php
@@ -574,6 +574,15 @@ class TWP_Call_Queue {
return;
}
+ // Send Discord/Slack notification for incoming call
+ require_once dirname(__FILE__) . '/class-twp-notifications.php';
+ TWP_Notifications::send_call_notification('incoming_call', array(
+ 'type' => 'incoming_call',
+ 'caller' => $caller_number,
+ 'queue' => $queue->queue_name,
+ 'queue_id' => $queue_id
+ ));
+
// Get members of the assigned agent group
require_once dirname(__FILE__) . '/class-twp-agent-groups.php';
$members = TWP_Agent_Groups::get_group_members($queue->agent_group_id);
diff --git a/includes/class-twp-core.php b/includes/class-twp-core.php
index a7479cf..9f6101f 100644
--- a/includes/class-twp-core.php
+++ b/includes/class-twp-core.php
@@ -167,6 +167,10 @@ class TWP_Core {
// Agent queue management AJAX
$this->loader->add_action('wp_ajax_twp_accept_call', $plugin_admin, 'ajax_accept_call');
+
+ // Discord/Slack notification system
+ $this->loader->add_action('init', $this, 'setup_notification_cron');
+ $this->loader->add_action('twp_check_queue_timeouts', $this, 'check_queue_timeouts');
$this->loader->add_action('wp_ajax_twp_accept_next_queue_call', $plugin_admin, 'ajax_accept_next_queue_call');
$this->loader->add_action('wp_ajax_twp_get_waiting_calls', $plugin_admin, 'ajax_get_waiting_calls');
$this->loader->add_action('wp_ajax_twp_set_agent_status', $plugin_admin, 'ajax_set_agent_status');
@@ -385,4 +389,21 @@ class TWP_Core {
error_log("TWP Cleanup: Updated {$updated_waiting} old waiting calls to 'timeout' status");
}
}
+
+ /**
+ * Setup notification cron job
+ */
+ public function setup_notification_cron() {
+ if (!wp_next_scheduled('twp_check_queue_timeouts')) {
+ wp_schedule_event(time(), 'twp_every_minute', 'twp_check_queue_timeouts');
+ }
+ }
+
+ /**
+ * Check for queue timeouts and send notifications
+ */
+ public function check_queue_timeouts() {
+ require_once dirname(__FILE__) . '/class-twp-notifications.php';
+ TWP_Notifications::check_queue_timeouts();
+ }
}
\ No newline at end of file
diff --git a/includes/class-twp-notifications.php b/includes/class-twp-notifications.php
new file mode 100644
index 0000000..2b6e9fe
--- /dev/null
+++ b/includes/class-twp-notifications.php
@@ -0,0 +1,328 @@
+ $message,
+ 'embeds' => array(
+ array(
+ 'title' => self::get_notification_title($data),
+ 'color' => self::get_notification_color($data),
+ 'fields' => self::get_discord_fields($data),
+ 'timestamp' => date('c'),
+ 'footer' => array(
+ 'text' => 'Twilio WP Plugin'
+ )
+ )
+ )
+ );
+
+ self::send_webhook_request($webhook_url, $payload, 'Discord');
+ }
+
+ /**
+ * Send notification to Slack
+ */
+ private static function send_slack_notification($webhook_url, $message, $data) {
+ $payload = array(
+ 'text' => self::get_notification_title($data),
+ 'attachments' => array(
+ array(
+ 'color' => self::get_slack_color($data),
+ 'fields' => self::get_slack_fields($data),
+ 'footer' => 'Twilio WP Plugin',
+ 'ts' => time()
+ )
+ )
+ );
+
+ self::send_webhook_request($webhook_url, $payload, 'Slack');
+ }
+
+ /**
+ * Get notification title
+ */
+ private static function get_notification_title($data) {
+ $type = isset($data['type']) ? $data['type'] : 'call_event';
+
+ switch ($type) {
+ case 'incoming_call':
+ return '📞 Incoming Call';
+ case 'queue_timeout':
+ return '⏰ Queue Timeout Alert';
+ case 'missed_call':
+ return '❌ Missed Call';
+ default:
+ return '📋 Call Event';
+ }
+ }
+
+ /**
+ * Get notification color for Discord (decimal)
+ */
+ private static function get_notification_color($data) {
+ $type = isset($data['type']) ? $data['type'] : 'call_event';
+
+ switch ($type) {
+ case 'incoming_call':
+ return 3447003; // Blue
+ case 'queue_timeout':
+ return 16776960; // Yellow
+ case 'missed_call':
+ return 15158332; // Red
+ default:
+ return 9807270; // Gray
+ }
+ }
+
+ /**
+ * Get notification color for Slack (hex)
+ */
+ private static function get_slack_color($data) {
+ $type = isset($data['type']) ? $data['type'] : 'call_event';
+
+ switch ($type) {
+ case 'incoming_call':
+ return '#36a64f'; // Green
+ case 'queue_timeout':
+ return '#ffcc00'; // Yellow
+ case 'missed_call':
+ return '#ff0000'; // Red
+ default:
+ return '#666666'; // Gray
+ }
+ }
+
+ /**
+ * Get Discord fields
+ */
+ private static function get_discord_fields($data) {
+ $fields = array();
+
+ if (isset($data['caller'])) {
+ $fields[] = array(
+ 'name' => 'Caller',
+ 'value' => $data['caller'],
+ 'inline' => true
+ );
+ }
+
+ if (isset($data['queue'])) {
+ $fields[] = array(
+ 'name' => 'Queue',
+ 'value' => $data['queue'],
+ 'inline' => true
+ );
+ }
+
+ if (isset($data['duration'])) {
+ $fields[] = array(
+ 'name' => 'Duration',
+ 'value' => $data['duration'] . ' seconds',
+ 'inline' => true
+ );
+ }
+
+ if (isset($data['workflow_number'])) {
+ $fields[] = array(
+ 'name' => 'Number Called',
+ 'value' => $data['workflow_number'],
+ 'inline' => true
+ );
+ }
+
+ return $fields;
+ }
+
+ /**
+ * Get Slack fields
+ */
+ private static function get_slack_fields($data) {
+ $fields = array();
+
+ if (isset($data['caller'])) {
+ $fields[] = array(
+ 'title' => 'Caller',
+ 'value' => $data['caller'],
+ 'short' => true
+ );
+ }
+
+ if (isset($data['queue'])) {
+ $fields[] = array(
+ 'title' => 'Queue',
+ 'value' => $data['queue'],
+ 'short' => true
+ );
+ }
+
+ if (isset($data['duration'])) {
+ $fields[] = array(
+ 'title' => 'Duration',
+ 'value' => $data['duration'] . ' seconds',
+ 'short' => true
+ );
+ }
+
+ if (isset($data['workflow_number'])) {
+ $fields[] = array(
+ 'title' => 'Number Called',
+ 'value' => $data['workflow_number'],
+ 'short' => true
+ );
+ }
+
+ return $fields;
+ }
+
+ /**
+ * Send webhook request
+ */
+ private static function send_webhook_request($webhook_url, $payload, $service) {
+ $response = wp_remote_post($webhook_url, array(
+ 'headers' => array(
+ 'Content-Type' => 'application/json',
+ ),
+ 'body' => json_encode($payload),
+ 'timeout' => 30,
+ ));
+
+ if (is_wp_error($response)) {
+ error_log("TWP {$service} Notification Error: " . $response->get_error_message());
+ return false;
+ }
+
+ $response_code = wp_remote_retrieve_response_code($response);
+ if ($response_code >= 200 && $response_code < 300) {
+ error_log("TWP {$service} notification sent successfully");
+ return true;
+ } else {
+ error_log("TWP {$service} notification failed with response code: " . $response_code);
+ error_log("Response body: " . wp_remote_retrieve_body($response));
+ return false;
+ }
+ }
+
+ /**
+ * Monitor queue timeouts
+ */
+ public static function check_queue_timeouts() {
+ global $wpdb;
+
+ $threshold = get_option('twp_queue_timeout_threshold', 300); // Default 5 minutes
+ $calls_table = $wpdb->prefix . 'twp_queued_calls';
+ $queues_table = $wpdb->prefix . 'twp_call_queues';
+
+ // Find calls that have been waiting too long
+ $timeout_calls = $wpdb->get_results($wpdb->prepare("
+ SELECT qc.*, q.queue_name
+ FROM {$calls_table} qc
+ LEFT JOIN {$queues_table} q ON qc.queue_id = q.id
+ WHERE qc.status = 'waiting'
+ AND TIMESTAMPDIFF(SECOND, qc.joined_at, NOW()) > %d
+ AND qc.notified_timeout IS NULL
+ ", $threshold));
+
+ foreach ($timeout_calls as $call) {
+ // Send timeout notification
+ self::send_call_notification('queue_timeout', array(
+ 'type' => 'queue_timeout',
+ 'caller' => $call->from_number,
+ 'queue' => $call->queue_name,
+ 'duration' => time() - strtotime($call->joined_at),
+ 'workflow_number' => $call->to_number
+ ));
+
+ // Mark as notified to avoid duplicate notifications
+ $wpdb->update(
+ $calls_table,
+ array('notified_timeout' => current_time('mysql')),
+ array('id' => $call->id),
+ array('%s'),
+ array('%d')
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/includes/class-twp-webhooks.php b/includes/class-twp-webhooks.php
index 1a56897..1448b1f 100644
--- a/includes/class-twp-webhooks.php
+++ b/includes/class-twp-webhooks.php
@@ -338,6 +338,15 @@ class TWP_Webhooks {
* Send SMS notification to agents about missed browser call
*/
private function send_agent_notification_sms($customer_number, $twilio_number) {
+ // Send Discord/Slack notification for missed browser call
+ require_once dirname(__FILE__) . '/class-twp-notifications.php';
+ TWP_Notifications::send_call_notification('missed_call', array(
+ 'type' => 'missed_call',
+ 'caller' => $customer_number,
+ 'queue' => 'Browser Phone',
+ 'workflow_number' => $twilio_number
+ ));
+
// Get agents with phone numbers
$agents = get_users(array(
'meta_key' => 'twp_phone_number',
@@ -556,6 +565,15 @@ class TWP_Webhooks {
* Send SMS notification to agents about missed call
*/
private function send_missed_call_notification($customer_number, $twilio_number) {
+ // Send Discord/Slack notification for missed call
+ require_once dirname(__FILE__) . '/class-twp-notifications.php';
+ TWP_Notifications::send_call_notification('missed_call', array(
+ 'type' => 'missed_call',
+ 'caller' => $customer_number,
+ 'queue' => 'General',
+ 'workflow_number' => $twilio_number
+ ));
+
// Get agents with phone numbers
$agents = get_users(array(
'meta_key' => 'twp_phone_number',