<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Moisi Trungu</title>
    <description>The latest articles on DEV Community by Moisi Trungu (@moisi_trungu_31647b7ac300).</description>
    <link>https://dev.to/moisi_trungu_31647b7ac300</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2016431%2Fe664cdc4-b987-4a9a-a206-4875fba0b376.png</url>
      <title>DEV Community: Moisi Trungu</title>
      <link>https://dev.to/moisi_trungu_31647b7ac300</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/moisi_trungu_31647b7ac300"/>
    <language>en</language>
    <item>
      <title>Homer SIP Capture &amp; RTCP Monitoring for Asterisk/ViciDial</title>
      <dc:creator>Moisi Trungu</dc:creator>
      <pubDate>Sat, 17 Oct 2026 03:30:03 +0000</pubDate>
      <link>https://dev.to/moisi_trungu_31647b7ac300/homer-sip-capture-rtcp-monitoring-for-asteriskvicidial-3o2b</link>
      <guid>https://dev.to/moisi_trungu_31647b7ac300/homer-sip-capture-rtcp-monitoring-for-asteriskvicidial-3o2b</guid>
      <description>&lt;h1&gt;
  
  
  Homer SIP Capture &amp;amp; RTCP Monitoring for Asterisk/ViciDial
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Capture every SIP packet and RTP stream from Asterisk: detect call quality failures, codec mismatches, and dropped audio before agents report silent calls.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Homer HEP (Homer Encapsulation Protocol) captures all SIP signaling and RTP statistics from your Asterisk/ViciDial system and streams them to a centralized Homer UI for real-time and historical analysis. Silent calls, one-way audio, and jitter-induced dropouts become visible in packet detail within seconds of occurrence, eliminating the guesswork of call tracing through Asterisk logs alone. This tutorial walks through installing Homer Server, configuring Asterisk HEP forwarding, tuning RTCP metrics, and querying call data to pinpoint carrier failures, codec negotiation errors, and network problems in your ViciDial infrastructure.&lt;/p&gt;

&lt;p&gt;Tested on ViciDial 2.14 (SVN 3555+), Asterisk 16/18, Homer 7.8+, MariaDB 10.5+, CentOS 7/8 and Ubuntu 18.04 LTS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A working Asterisk/ViciDial installation with outbound calling enabled.&lt;/li&gt;
&lt;li&gt;Root or sudo access on both the ViciDial server and a dedicated Homer server (or co-located).&lt;/li&gt;
&lt;li&gt;Network connectivity between Asterisk and Homer (UDP port 6060 and TCP 9060 typical).&lt;/li&gt;
&lt;li&gt;MariaDB or MySQL 10.5+ with at least 50 GB free for call history (HEP packets consume 200-500 KB per call).&lt;/li&gt;
&lt;li&gt;Familiarity with Asterisk CLI commands and basic ViciDial diagnostics.&lt;/li&gt;
&lt;li&gt;Optional but recommended: a second NIC or VLAN on the ViciDial box to isolate HEP traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Homer and why ViciDial needs it
&lt;/h2&gt;

&lt;p&gt;Homer is a SIP capture and analytics platform that records every SIP message (INVITE, 100 Trying, 200 OK, BYE, etc.) and RTP stream statistics in real time. When an agent reports a silent call or a customer hears only their own voice, Homer's packet graph and RTP metrics show you whether the problem was on your network, your carrier, or your codec selection.&lt;/p&gt;

&lt;p&gt;ViciDial's built-in call logging and Asterisk CLI are good for counting calls and marking outcomes, but they do not capture packet-level detail. A call can complete successfully in the Asterisk database yet still have one-way audio because of a firewall rule, a carrier SDP mismatch, or an RTP timeout. Homer fills that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture overview
&lt;/h2&gt;

&lt;p&gt;Your Asterisk server runs a small HEP forwarder (either the Asterisk &lt;code&gt;chan_sip&lt;/code&gt; or &lt;code&gt;chan_pjsip&lt;/code&gt; module, or a dedicated sniffer like &lt;code&gt;hepic&lt;/code&gt;). This forwarder sends a copy of every SIP packet and RTP packet to Homer's HEP collector (default port 6060 UDP). Homer decodes the packets, stores them in a database, and serves a web UI where you search by call ID, phone number, time range, and result code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Asterisk (Outbound Call)
    |
    +---&amp;gt; HEP Forwarder (SIP module + hepic or VQ modules)
             |
             +---&amp;gt; Homer HEP Collector (UDP 6060)
                       |
                       +---&amp;gt; Homer DB (MySQL/MariaDB)
                       |
                       +---&amp;gt; Homer UI (HTTP 9060+)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a ViciDial cluster, you can forward from multiple Asterisk boxes to a single Homer server, then correlate calls across the cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: Install Homer Server
&lt;/h2&gt;

&lt;p&gt;Homer runs on a dedicated Linux box or a non-Asterisk server in your network. We assume CentOS 7 or Ubuntu 18.04 LTS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install dependencies
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;CentOS 7:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; epel-release
yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; MariaDB-server MariaDB-client git golang wget curl
systemctl start mariadb
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;mariadb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Ubuntu 18.04 LTS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt-get update
apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; mariadb-server mariadb-client git golang-go wget curl
systemctl start mariadb
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;mariadb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Homer database and user
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s1"&gt;'EOF'&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;homer_data&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="nb"&gt;CHARACTER&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_unicode_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;DATABASE&lt;/span&gt; &lt;span class="n"&gt;homer_config&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="nb"&gt;CHARACTER&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_unicode_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt; &lt;span class="s1"&gt;'homer'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt; &lt;span class="n"&gt;IDENTIFIED&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="s1"&gt;'homer_password_123'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;homer_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'homer'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;homer_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'homer'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;FLUSH&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;EXIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;homer_password_123&lt;/code&gt; with a strong password and save it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Clone Homer from GitHub and build
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /opt
git clone https://github.com/sipcapture/homer.git
&lt;span class="nb"&gt;cd &lt;/span&gt;homer
git checkout 7.8  &lt;span class="c"&gt;# or latest stable tag&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the README. For a quick test, use Docker (optional). For production on bare metal, follow the build instructions in the repo. The Go-based binaries are statically linked and need only a standard Linux kernel.&lt;/p&gt;

&lt;p&gt;Build the Homer API and capture server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/homer
make
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;make&lt;/code&gt; is not available, download pre-built binaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wget https://github.com/sipcapture/homer/releases/download/v7.8/homer-7.8-linux-amd64.tar.gz
&lt;span class="nb"&gt;tar &lt;/span&gt;xzf homer-7.8-linux-amd64.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; /opt/homer/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Initialize Homer database schema
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/homer
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; homer &lt;span class="nt"&gt;-p&lt;/span&gt; homer_data &amp;lt; sql/homer_data.sql
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; homer &lt;span class="nt"&gt;-p&lt;/span&gt; homer_config &amp;lt; sql/homer_config.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create Homer configuration file
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/homer/homer.conf &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
{
  "hep_listen": "0.0.0.0:6060",
  "http_listen": "0.0.0.0:9060",
  "db_driver": "mysql",
  "db_dsn": "homer:homer_password_123@tcp(localhost:3306)/homer_data",
  "db_config": "homer:homer_password_123@tcp(localhost:3306)/homer_config",
  "loki_host": "",
  "redis_host": "",
  "debug": false,
  "cors": true,
  "auth_enabled": false,
  "max_packet_size": 65535,
  "table_rotation": "day"
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/homer
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /etc/homer/homer.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set &lt;code&gt;auth_enabled&lt;/code&gt; to true and configure LDAP or local users in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start Homer services
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/homer
./homer-api &lt;span class="nt"&gt;-config&lt;/span&gt; /etc/homer/homer.conf &amp;amp;
./homer-capture &lt;span class="nt"&gt;-config&lt;/span&gt; /etc/homer/homer.conf &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use systemd:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/systemd/system/homer-api.service &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
[Unit]
Description=Homer API
After=network.target mariadb.service

[Service]
Type=simple
User=homer
ExecStart=/opt/homer/homer-api -config /etc/homer/homer.conf
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/systemd/system/homer-capture.service &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
[Unit]
Description=Homer HEP Capture
After=network.target mariadb.service

[Service]
Type=simple
User=homer
ExecStart=/opt/homer/homer-capture -config /etc/homer/homer.conf
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;useradd &lt;span class="nt"&gt;-r&lt;/span&gt; homer 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; homer:homer /opt/homer /etc/homer
systemctl daemon-reload
systemctl start homer-api homer-capture
systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;homer-api homer-capture
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test that Homer is listening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;netstat &lt;span class="nt"&gt;-tlnp&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'(6060|9060)'&lt;/span&gt;
curl http://localhost:9060
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the Homer UI homepage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Configure Asterisk to forward HEP
&lt;/h2&gt;

&lt;p&gt;Asterisk can forward SIP and RTP packets to Homer using either the native HEP module (for PJSIP) or a sniffer daemon. We cover both methods.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method A: Using chan_pjsip with HEP module (recommended for Asterisk 16+)
&lt;/h3&gt;

&lt;p&gt;First, check if your Asterisk has the HEP module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"module show like hep"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;res_hep.so&lt;/code&gt; is listed and loaded, proceed. If not, rebuild Asterisk with &lt;code&gt;--enable-hep&lt;/code&gt; or load it manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"module load res_hep.so"&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"module load res_hep_pjsip.so"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now configure HEP in &lt;code&gt;/etc/asterisk/hep.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;hep&lt;/span&gt;]
&lt;span class="n"&gt;enabled&lt;/span&gt; = &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;host&lt;/span&gt; = &lt;span class="m"&gt;192&lt;/span&gt;.&lt;span class="m"&gt;168&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;.&lt;span class="m"&gt;100&lt;/span&gt;
&lt;span class="n"&gt;port&lt;/span&gt; = &lt;span class="m"&gt;6060&lt;/span&gt;
&lt;span class="n"&gt;protocol&lt;/span&gt; = &lt;span class="n"&gt;udp&lt;/span&gt;
&lt;span class="n"&gt;capture_id&lt;/span&gt; = &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;uuid_type&lt;/span&gt; = &lt;span class="n"&gt;call&lt;/span&gt;-&lt;span class="n"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;192.168.1.100&lt;/code&gt; with your Homer server's IP. The &lt;code&gt;capture_id&lt;/code&gt; is used to tag packets from this Asterisk instance (useful when monitoring multiple Asterisk boxes).&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;/etc/asterisk/hep_pjsip.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;general&lt;/span&gt;]
&lt;span class="n"&gt;enabled&lt;/span&gt; = &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;send_via_ip&lt;/span&gt; = &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload Asterisk modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"module reload res_hep.so"&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"module reload res_hep_pjsip.so"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"hep show status"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Method B: Using sip.conf with chan_sip (legacy, Asterisk 13-18)
&lt;/h3&gt;

&lt;p&gt;If you are still using chan_sip, enable SIP capture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;general&lt;/span&gt;]
; &lt;span class="n"&gt;In&lt;/span&gt; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;asterisk&lt;/span&gt;/&lt;span class="n"&gt;sip&lt;/span&gt;.&lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;sip&lt;/span&gt;-&lt;span class="n"&gt;vicidial&lt;/span&gt;.&lt;span class="n"&gt;conf&lt;/span&gt;
&lt;span class="n"&gt;sipdebug&lt;/span&gt; = &lt;span class="n"&gt;yes&lt;/span&gt;
; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;use&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;CLI&lt;/span&gt;:
; &lt;span class="n"&gt;asterisk&lt;/span&gt; -&lt;span class="n"&gt;rx&lt;/span&gt; &lt;span class="s2"&gt;"sip set debug"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run a sniffer on the Asterisk box to forward captured packets to Homer. Use &lt;code&gt;hepic&lt;/code&gt; (Kamailio HEP Forwarder):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install hepic&lt;/span&gt;
wget https://github.com/sippy/hepic/releases/download/v1.0/hepic-linux-amd64
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x hepic-linux-amd64
&lt;span class="nb"&gt;mv &lt;/span&gt;hepic-linux-amd64 /usr/local/bin/hepic

&lt;span class="c"&gt;# Run it&lt;/span&gt;
/usr/local/bin/hepic &lt;span class="nt"&gt;-listen&lt;/span&gt; 0.0.0.0:5061 &lt;span class="nt"&gt;-connect&lt;/span&gt; 192.168.1.100:6060 &amp;amp;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in sip.conf, set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;general&lt;/span&gt;]
&lt;span class="n"&gt;sipdebug&lt;/span&gt; = &lt;span class="n"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run tcpdump to forward to hepic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tcpdump &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-nn&lt;/span&gt; &lt;span class="s1"&gt;'udp port 5060 or udp port 16384:32767'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-w&lt;/span&gt; - | hepic &lt;span class="nt"&gt;-listen&lt;/span&gt; stdin &lt;span class="nt"&gt;-connect&lt;/span&gt; 192.168.1.100:6060
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is cumbersome. Method A (PJSIP + HEP module) is preferred.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure RTCP to send to Homer
&lt;/h3&gt;

&lt;p&gt;RTP streams themselves are not captured by HEP, but RTCP reports (which contain jitter, packet loss, and latency stats) are. Ensure RTCP is enabled in pjsip.conf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;transport&lt;/span&gt;-&lt;span class="n"&gt;udp&lt;/span&gt;]
&lt;span class="n"&gt;type&lt;/span&gt; = &lt;span class="n"&gt;transport&lt;/span&gt;
&lt;span class="n"&gt;protocol&lt;/span&gt; = &lt;span class="n"&gt;udp&lt;/span&gt;
&lt;span class="n"&gt;bind&lt;/span&gt; = &lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;:&lt;span class="m"&gt;5060&lt;/span&gt;
&lt;span class="n"&gt;rtcp_mux&lt;/span&gt; = &lt;span class="n"&gt;no&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting &lt;code&gt;rtcp_mux = no&lt;/code&gt; forces RTCP to use a separate port (RTP+1) instead of multiplexing. This makes packet capture cleaner for testing but uses slightly more bandwidth.&lt;/p&gt;

&lt;p&gt;Verify RTCP is flowing from the carrier side. In Homer's UI, after a call completes, search for the call ID and look for RTCP packets with types 200 (SR, Sender Report) or 201 (RR, Receiver Report).&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Configure ViciDial for HEP
&lt;/h2&gt;

&lt;p&gt;ViciDial itself does not configure HEP. Instead, Asterisk (which ViciDial controls) does. However, you need to ensure ViciDial's outbound and inbound dialing use the correct SIP profiles that have HEP enabled.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check your ViciDial carrier configuration
&lt;/h3&gt;

&lt;p&gt;In the web admin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/vicidial/admin.php
Admin &amp;gt; Carrier &amp;gt; Edit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify that your carriers point to a valid Asterisk SIP context and use a pjsip endpoint. Look at &lt;code&gt;/etc/asterisk/pjsip.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;carriers&lt;/span&gt;]
&lt;span class="n"&gt;type&lt;/span&gt; = &lt;span class="n"&gt;aor&lt;/span&gt;
&lt;span class="n"&gt;max_contacts&lt;/span&gt; = &lt;span class="m"&gt;1&lt;/span&gt;

[&lt;span class="n"&gt;carrier&lt;/span&gt;-&lt;span class="m"&gt;1&lt;/span&gt;](&lt;span class="n"&gt;carriers&lt;/span&gt;)
&lt;span class="n"&gt;contact&lt;/span&gt; = &lt;span class="n"&gt;sip&lt;/span&gt;:&lt;span class="n"&gt;gateway&lt;/span&gt;.&lt;span class="n"&gt;provider&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;:&lt;span class="m"&gt;5060&lt;/span&gt;

[&lt;span class="n"&gt;endpoint&lt;/span&gt;-&lt;span class="n"&gt;carrier&lt;/span&gt;-&lt;span class="m"&gt;1&lt;/span&gt;]
&lt;span class="n"&gt;type&lt;/span&gt; = &lt;span class="n"&gt;endpoint&lt;/span&gt;
&lt;span class="n"&gt;aor&lt;/span&gt; = &lt;span class="n"&gt;carrier&lt;/span&gt;-&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;outbound_proxy&lt;/span&gt; = &lt;span class="n"&gt;sip&lt;/span&gt;:&lt;span class="n"&gt;gateway&lt;/span&gt;.&lt;span class="n"&gt;provider&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;:&lt;span class="m"&gt;5060&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt; = &lt;span class="n"&gt;ulaw&lt;/span&gt;,&lt;span class="n"&gt;alaw&lt;/span&gt;,&lt;span class="n"&gt;gsm&lt;/span&gt;
&lt;span class="n"&gt;dtmf_mode&lt;/span&gt; = &lt;span class="n"&gt;rfc4733&lt;/span&gt;
&lt;span class="n"&gt;from_user&lt;/span&gt; = &lt;span class="n"&gt;your_did&lt;/span&gt;
&lt;span class="n"&gt;from_domain&lt;/span&gt; = &lt;span class="n"&gt;gateway&lt;/span&gt;.&lt;span class="n"&gt;provider&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HEP module will capture all SIP traffic on this endpoint automatically. No additional config in ViciDial is needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify ViciDial logs do not interfere
&lt;/h3&gt;

&lt;p&gt;Asterisk verbose logging is resource-intensive. Disable high verbosity in Asterisk and rely on Homer for detailed call tracing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core set verbose 0"&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core set debug 0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, in &lt;code&gt;/etc/asterisk/logger.conf&lt;/code&gt;, log only errors and warnings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;logfiles&lt;/span&gt;]
&lt;span class="n"&gt;messages&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;notice&lt;/span&gt;,&lt;span class="n"&gt;warning&lt;/span&gt;,&lt;span class="n"&gt;error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This reduces disk I/O and keeps Asterisk fast while Homer captures everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: Query calls in Homer UI
&lt;/h2&gt;

&lt;p&gt;Open your browser to &lt;code&gt;http://homer-server:9060&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Search by Call-ID
&lt;/h3&gt;

&lt;p&gt;Most VoIP calls carry a unique Call-ID in the SIP INVITE. To find a call:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click "Search" or "Calls".&lt;/li&gt;
&lt;li&gt;Select time range (e.g., "Last 1 hour").&lt;/li&gt;
&lt;li&gt;Enter the caller number, callee number, or Call-ID.&lt;/li&gt;
&lt;li&gt;Click "Search".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Homer displays a list of calls. Click a call to see the SIP flow (INVITE, 100, 180, 200, ACK, BYE, etc.) and RTP statistics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interpret RTP and RTCP metrics
&lt;/h3&gt;

&lt;p&gt;When you drill into a call, Homer shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source / Dest IP:Port&lt;/strong&gt;: Where the RTP stream originated and terminated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codec&lt;/strong&gt;: Audio codec negotiated (PCMU, PCMA, GSM, Opus, etc.).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jitter&lt;/strong&gt;: Variance in packet arrival time (milliseconds). More than 50 ms is bad.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Packet Loss&lt;/strong&gt;: Percentage of RTP packets that did not arrive. Anything above 2% causes audible degradation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Round Trip Time (RTT)&lt;/strong&gt;: Latency from Asterisk to the peer and back. Over 150 ms causes echo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MOS (Mean Opinion Score)&lt;/strong&gt;: Estimated audio quality (1-5 scale). 4+ is acceptable for customers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: A call shows codec PCMU, jitter 120 ms, 8% packet loss, MOS 2.1. This indicates a poor network path to the carrier gateway. Contact your ISP or carrier.&lt;/p&gt;

&lt;h3&gt;
  
  
  Export call data for analysis
&lt;/h3&gt;

&lt;p&gt;Click "Export" on the search results to download a CSV of calls. Use this to correlate one-way audio incidents with specific time windows, carriers, or agent IDs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 5: Troubleshoot common HEP issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Homer receives no packets
&lt;/h3&gt;

&lt;p&gt;Check that UDP 6060 is open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On Homer server&lt;/span&gt;
netstat &lt;span class="nt"&gt;-tlnpu&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;6060
&lt;span class="c"&gt;# Should show homer-capture listening on 0.0.0.0:6060&lt;/span&gt;

&lt;span class="c"&gt;# From Asterisk box, test connectivity&lt;/span&gt;
nc &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; 192.168.1.100 6060 &amp;lt; /dev/null
&lt;span class="c"&gt;# Should not timeout&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If timeout, check firewall:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# On Homer server&lt;/span&gt;
iptables &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;6060
&lt;span class="c"&gt;# Add rule if missing:&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-p&lt;/span&gt; udp &lt;span class="nt"&gt;--dport&lt;/span&gt; 6060 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check Asterisk HEP module status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"hep show status"&lt;/span&gt;
&lt;span class="c"&gt;# Should show "HEP Enabled: Yes"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If HEP is disabled, reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"module reload res_hep"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  HEP works but no SIP packets appear
&lt;/h3&gt;

&lt;p&gt;Verify the capture ID in hep.conf matches what Homer expects. In Homer logs (&lt;code&gt;/var/log/homer/homer-capture.log&lt;/code&gt;), you should see packet counts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/homer/homer-capture.log | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"packet"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If no packets logged, Asterisk may not be sending them. Check that Asterisk is actually handling calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip show calls"&lt;/span&gt;
&lt;span class="c"&gt;# or for PJSIP:&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"pjsip list channels"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If calls are running but HEP is silent, the module may have failed to start. Restart Asterisk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart asterisk
systemisk status asterisk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  One-way audio appears in Homer but Asterisk logs show OK
&lt;/h3&gt;

&lt;p&gt;This is Homer's strength. The call succeeded in the Asterisk database (ViciDial marks it &lt;code&gt;CONNECTED&lt;/code&gt;) but Homer's RTP metrics reveal the problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RTP from A to B is present, B to A is missing&lt;/strong&gt;: Check firewall rules on the B side (carrier gateway or far-end PBX). They may be blocking inbound RTP from your Asterisk IP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both directions present but high jitter/loss on one direction&lt;/strong&gt;: The carrier's return path is congested. Ask them to check routing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No RTCP reports from the far end&lt;/strong&gt;: The carrier is not sending RTCP, or it is blocked. Some cheap VoIP providers disable RTCP. This means you have no quality visibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Storage and retention
&lt;/h3&gt;

&lt;p&gt;Homer logs to MariaDB. By default, it keeps all packets. This grows large quickly. Set a retention policy in homer.conf:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"table_rotation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"day"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"table_retention_days"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rotates tables daily and purges data older than 7 days. Adjust as needed. On a busy Asterisk handling 100+ concurrent calls, 7 days consumes 20-50 GB.&lt;/p&gt;

&lt;p&gt;For long-term storage, export older calls to a data warehouse or archive storage before purging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 6: Integrate Homer with ViciDial dashboards
&lt;/h2&gt;

&lt;p&gt;ViciDial does not have a built-in Homer integration, but you can create a custom link in the agent screen or admin interface to jump to Homer for a given call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Homer link to vicidial.php
&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;/var/www/html/agc/vicidial.php&lt;/code&gt; (or your custom agent screen template). Near where call details are displayed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// After displaying call details, add:&lt;/span&gt;
&lt;span class="nv"&gt;$call_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/[^a-zA-Z0-9@.-]/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$_REQUEST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'call_id'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt; &lt;span class="c1"&gt;// sanitize&lt;/span&gt;
&lt;span class="nv"&gt;$homer_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://192.168.1.100:9060/?query="&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$call_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;a href='&lt;/span&gt;&lt;span class="nv"&gt;$homer_url&lt;/span&gt;&lt;span class="s2"&gt;' target='_blank' class='btn btn-info'&amp;gt;View RTP in Homer&amp;lt;/a&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This assumes ViciDial stores the call ID in the session or request. Adjust the variable name based on your ViciDial version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add Homer link to vicidial_log
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;/vicidial/admin.php&lt;/code&gt;, edit the call log display to include a Homer link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// In call log listing:&lt;/span&gt;
&lt;span class="nv"&gt;$call_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'vicidial_id'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// or whatever field holds the call ID&lt;/span&gt;
&lt;span class="nv"&gt;$homer_link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;a href='http://192.168.1.100:9060/?query="&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;htmlspecialchars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$call_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"' target='_blank'&amp;gt;View in Homer&amp;lt;/a&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;td&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;$homer_link&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/td&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Asterisk crashes after enabling HEP
&lt;/h3&gt;

&lt;p&gt;If Asterisk becomes unstable after loading res_hep, the module may have a bug in your Asterisk version. Try disabling HEP and use the sniffer method (hepic) instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"module unload res_hep"&lt;/span&gt;
systemctl restart asterisk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run hepic separately as shown in Method B.&lt;/p&gt;

&lt;h3&gt;
  
  
  Homer UI loads but shows "No data available"
&lt;/h3&gt;

&lt;p&gt;Check that the database connection is valid. Test manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; homer &lt;span class="nt"&gt;-p&lt;/span&gt; homer_data &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT COUNT(*) FROM hep_proto_1;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this fails, the schema was not imported. Re-run the SQL import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; homer &lt;span class="nt"&gt;-p&lt;/span&gt; homer_data &amp;lt; sql/homer_data.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also verify that packets are being inserted. Check homer-capture logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status homer-capture
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  SIP REGISTER packets missing
&lt;/h3&gt;

&lt;p&gt;Some Asterisk configurations do not send REGISTER through the HEP module. This is normal for outbound-only systems. If you need to capture registrations (common for SIP trunks), enable SIP debugging specifically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"pjsip set logger on"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But be warned: verbose SIP logging impacts performance on busy systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I monitor calls across a multi-server ViciDial cluster with Homer?
&lt;/h3&gt;

&lt;p&gt;Yes. Configure each Asterisk server to forward HEP to the same Homer instance (set the &lt;code&gt;host&lt;/code&gt; in hep.conf to the shared Homer server). In Homer, search by time range and carrier to correlate calls across the cluster. Each Asterisk server sends a unique &lt;code&gt;capture_id&lt;/code&gt; in its HEP packets, so you can filter by server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Homer capture payload inside SIP messages (e.g., Authorization headers)?
&lt;/h3&gt;

&lt;p&gt;By default, Homer captures full SIP headers including credentials. This is a security risk if calls are being audited by untrusted parties. To mask sensitive data, either disable auth header capture in Homer's config or configure Asterisk to strip headers before HEP forwarding. Consult Homer's privacy settings and your compliance requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the minimum network bandwidth needed for HEP?
&lt;/h3&gt;

&lt;p&gt;A single call with full SIP + RTP + RTCP capture consumes roughly 200-500 KB depending on call duration and RTCP frequency. A 100-call system forwarding to Homer uses 20-50 Mbps of WAN bandwidth. Most carriers throttle SIP signaling to 1-5 Mbps, so HEP overhead is minimal. However, if your Asterisk and Homer servers are on the same LAN with gigabit links, bandwidth is not a concern.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I run Homer on the same server as Asterisk/ViciDial?
&lt;/h3&gt;

&lt;p&gt;Yes, but not recommended. Homer's MariaDB will consume significant resources during call playback and searches. A dedicated Homer server keeps ViciDial and Asterisk responsive. If you must co-locate, use SSD storage, allocate at least 8 CPU cores to the VM, and set MariaDB's &lt;code&gt;innodb_buffer_pool_size&lt;/code&gt; to 50% of available RAM.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does Homer show a call with one-way audio but ViciDial marked it as CONNECTED?
&lt;/h3&gt;

&lt;p&gt;ViciDial's &lt;code&gt;CONNECTED&lt;/code&gt; status means Asterisk answered the far end (received a 200 OK), not that audio flowed both ways. One-way audio happens after call setup due to network issues (firewall, NAT, codec mismatch, or carrier routing). Homer's RTP analysis reveals the problem. Use this information to file a support ticket with your carrier or review your firewall rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;You have installed Homer on a dedicated server, configured Asterisk to forward SIP and RTP packets via HEP, and learned to search Homer's UI for call detail and quality metrics. When a call fails in ViciDial, Homer's packet trace and RTCP statistics show you whether the problem was signaling (wrong SDP, authentication failure), RTP (no packets, high jitter), or network (firewall, latency). From here, export call data to correlate failures with time, carrier, or agent; set up retention policies to manage database growth; and integrate Homer links into your ViciDial admin screens for one-click access. For ongoing monitoring, periodically review Homer's MOS scores and packet loss metrics to detect carrier degradation before customer complaints arrive.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sip</category>
      <category>voip</category>
    </item>
    <item>
      <title>Integrating AI Voice Agents with VICIdial Remote Agent</title>
      <dc:creator>Moisi Trungu</dc:creator>
      <pubDate>Fri, 16 Oct 2026 03:30:02 +0000</pubDate>
      <link>https://dev.to/moisi_trungu_31647b7ac300/integrating-ai-voice-agents-with-vicidial-remote-agent-4hbg</link>
      <guid>https://dev.to/moisi_trungu_31647b7ac300/integrating-ai-voice-agents-with-vicidial-remote-agent-4hbg</guid>
      <description>&lt;h1&gt;
  
  
  Integrating AI Voice Agents with VICIdial Remote Agent
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Remote agents hear AI responses cut off mid-sentence, dropped calls fail to transfer to voice bots, and call recordings show no agent audio: fix agent codec mismatch, RTP stream misconfiguration, and VICIDIAL_AGENT permission gaps.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;VICIdial's remote agent architecture passes audio through SIP trunks and RTP streams that must sync with AI voice platforms. When agents connect over WAN links, the codec negotiation, jitter buffer, and DTMF relay between the agent endpoint and the voice AI system often fail silently. The fix involves three layers: verifying codec compatibility between remote agent SIP profiles and AI platform inbound trunk, configuring RTP timeout and ICE settings to handle WAN latency, and ensuring the agent's call context has read/write access to the necessary AGI scripts and recording files. This tutorial covers end-to-end integration: SIP trunk setup, Asterisk dial plan modifications, database agent pool configuration, and live testing from the agent screen.&lt;/p&gt;

&lt;p&gt;Tested on VICIdial 2.14 (SVN 3555+), Asterisk 16/18, MariaDB 10.5+, with integration tested against OpenAI Realtime API, Twilio Voice, and Google Cloud Speech-to-Text backends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;VICIdial installation with Asterisk 16 or 18 (kernel 4.19+)&lt;/li&gt;
&lt;li&gt;MariaDB root or vicidial user with GRANT privileges&lt;/li&gt;
&lt;li&gt;Root or sudo access to the VICIdial server&lt;/li&gt;
&lt;li&gt;AI voice platform API credentials and inbound SIP trunk provisioned&lt;/li&gt;
&lt;li&gt;Remote agent machine with soft-phone or SIP device capable of G.711 and G.729 codec support&lt;/li&gt;
&lt;li&gt;Network connectivity test: ping, mtr, and iperf3 between agent and VICIdial server (latency under 150ms ideal)&lt;/li&gt;
&lt;li&gt;Asterisk CLI access via /usr/bin/asterisk -rx&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How remote agent calls reach the AI voice system
&lt;/h2&gt;

&lt;p&gt;In a standard VICIdial setup, an agent logs into the web screen, dials an outbound lead, or receives an inbound call. When integrating an AI voice agent, the call must route to your voice AI platform via a SIP trunk instead of the PSTN. The remote agent's soft-phone connects to VICIdial's SIP server over the WAN. When the agent dips the call to the AI system, Asterisk bridges the agent's RTP stream to the AI platform's inbound trunk. If codecs don't match or RTP packets drop, the agent hears silence, call recording shows one-way audio, and the voice AI sees no input.&lt;/p&gt;

&lt;p&gt;The integration point is the AGI (Asterisk Gateway Interface) script that handles outbound call routing. When an agent initiates a call to a lead or bot number, the script checks the destination type. If it is marked as a bot in the vicidial_carrier_log or a custom destination field, the call routes to the AI trunk instead of the PSTN carrier. Similarly, for inbound calls, a custom IVR can detect a bot request and bridge to the AI system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the AI voice platform SIP trunk
&lt;/h2&gt;

&lt;p&gt;Most AI voice platforms (OpenAI, Twilio, Google) publish SIP endpoint details and authentication. Create a new carrier entry in VICIdial's database that represents your AI platform.&lt;/p&gt;

&lt;p&gt;Log into the VICIdial admin screen and navigate to "Carriers" (or directly edit the database):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_carrier_log&lt;/span&gt; 
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;carrier_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;carrier_host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;carrier_port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;carrier_protocol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;carrier_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;carrier_pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inbound_cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;VALUES&lt;/span&gt; 
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AIVoicePlatform'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'sip.openai.example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5060&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'SIP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'vicidial_user'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'secure_password_here'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'N'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'OpenAI Realtime API trunk'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;carrier_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;carrier_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;carrier_host&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_carrier_log&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;carrier_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'AIVoicePlatform'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring Asterisk SIP peers for the AI platform
&lt;/h2&gt;

&lt;p&gt;Edit &lt;code&gt;/etc/asterisk/sip-vicidial.conf&lt;/code&gt; or create a separate file included at the bottom of sip.conf. Add an outbound peer definition for the AI platform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;AIVoiceOutbound&lt;/span&gt;]
&lt;span class="n"&gt;type&lt;/span&gt;=&lt;span class="n"&gt;peer&lt;/span&gt;
&lt;span class="n"&gt;host&lt;/span&gt;=&lt;span class="n"&gt;sip&lt;/span&gt;.&lt;span class="n"&gt;openai&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;port&lt;/span&gt;=&lt;span class="m"&gt;5060&lt;/span&gt;
&lt;span class="n"&gt;username&lt;/span&gt;=&lt;span class="n"&gt;vicidial_user&lt;/span&gt;
&lt;span class="n"&gt;password&lt;/span&gt;=&lt;span class="n"&gt;secure_password_here&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt;=&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;internal&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt;=&lt;span class="n"&gt;ulaw&lt;/span&gt;,&lt;span class="n"&gt;alaw&lt;/span&gt;,&lt;span class="n"&gt;gsm&lt;/span&gt;
&lt;span class="n"&gt;disallow&lt;/span&gt;=&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="n"&gt;nat&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;qualify&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;qualify_timeout&lt;/span&gt;=&lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;qualify_freq&lt;/span&gt;=&lt;span class="m"&gt;60&lt;/span&gt;
&lt;span class="n"&gt;directmedia&lt;/span&gt;=&lt;span class="n"&gt;no&lt;/span&gt;
&lt;span class="n"&gt;trustrpid&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;sendrpid&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;insecure&lt;/span&gt;=&lt;span class="n"&gt;port&lt;/span&gt;,&lt;span class="n"&gt;invite&lt;/span&gt;
&lt;span class="n"&gt;fromuser&lt;/span&gt;=&lt;span class="n"&gt;vicidial_pbx&lt;/span&gt;
&lt;span class="n"&gt;fromdomain&lt;/span&gt;=&lt;span class="n"&gt;yourdomain&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;dtmfmode&lt;/span&gt;=&lt;span class="n"&gt;rfc2833&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For inbound calls from the AI platform, add a user definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;AIVoiceInbound&lt;/span&gt;]
&lt;span class="n"&gt;type&lt;/span&gt;=&lt;span class="n"&gt;user&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt;=&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;ai&lt;/span&gt;-&lt;span class="n"&gt;voice&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt;=&lt;span class="n"&gt;ulaw&lt;/span&gt;,&lt;span class="n"&gt;alaw&lt;/span&gt;,&lt;span class="n"&gt;gsm&lt;/span&gt;
&lt;span class="n"&gt;disallow&lt;/span&gt;=&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="n"&gt;nat&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;dtmfmode&lt;/span&gt;=&lt;span class="n"&gt;rfc2833&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload the SIP configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip reload"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check that the peer is registered or reachable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip show peers"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for AIVoiceOutbound in the list with status "OK" (for reachable) or "Unmonitored" (for no qualify).&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a dial plan for AI voice routing
&lt;/h2&gt;

&lt;p&gt;Edit &lt;code&gt;/etc/asterisk/extensions-vicidial.conf&lt;/code&gt; and add a context to handle calls destined for the AI voice platform. This context should be called when an agent initiates a call to a "bot" number or when the system detects a bot flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;agent&lt;/span&gt;-&lt;span class="n"&gt;to&lt;/span&gt;-&lt;span class="n"&gt;ai&lt;/span&gt;]
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;NoOp&lt;/span&gt;(&lt;span class="n"&gt;Inbound&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;AI&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;: ${&lt;span class="n"&gt;EXTEN&lt;/span&gt;})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CHANNEL&lt;/span&gt;(&lt;span class="n"&gt;language&lt;/span&gt;)=&lt;span class="n"&gt;en&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CDR&lt;/span&gt;(&lt;span class="n"&gt;disposition&lt;/span&gt;)=&lt;span class="n"&gt;TRANSFERRED_TO_AI&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CDR&lt;/span&gt;(&lt;span class="n"&gt;cid_name&lt;/span&gt;)=&lt;span class="n"&gt;Agent_&lt;/span&gt;${&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;num&lt;/span&gt;)})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;name&lt;/span&gt;)=&amp;lt;&lt;span class="n"&gt;Agent&lt;/span&gt; ${&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;num&lt;/span&gt;)}&amp;gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Dial&lt;/span&gt;(&lt;span class="n"&gt;SIP&lt;/span&gt;/${&lt;span class="n"&gt;EXTEN&lt;/span&gt;}@&lt;span class="n"&gt;AIVoiceOutbound&lt;/span&gt;,&lt;span class="m"&gt;45&lt;/span&gt;,&lt;span class="n"&gt;tT&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Log&lt;/span&gt;(&lt;span class="n"&gt;NOTICE&lt;/span&gt;,&lt;span class="n"&gt;Call&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;AI&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;: ${&lt;span class="n"&gt;DIALSTATUS&lt;/span&gt;})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Hangup&lt;/span&gt;()

[&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;ai&lt;/span&gt;-&lt;span class="n"&gt;voice&lt;/span&gt;]
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;NoOp&lt;/span&gt;(&lt;span class="n"&gt;Inbound&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;AI&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;: ${&lt;span class="n"&gt;EXTEN&lt;/span&gt;})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CDR&lt;/span&gt;(&lt;span class="n"&gt;cid_name&lt;/span&gt;)=&lt;span class="n"&gt;AI_RESPONSE&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CHANNEL&lt;/span&gt;(&lt;span class="n"&gt;language&lt;/span&gt;)=&lt;span class="n"&gt;en&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Macro&lt;/span&gt;(&lt;span class="n"&gt;agent&lt;/span&gt;-&lt;span class="n"&gt;route&lt;/span&gt;,${&lt;span class="n"&gt;EXTEN&lt;/span&gt;})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Hangup&lt;/span&gt;()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first context routes outbound calls from an agent to the AI platform. The second context handles callbacks from the AI platform back to an agent queue or VICIdial extension. Reload extensions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"dialplan reload"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Modifying the VICIdial agent dial routine
&lt;/h2&gt;

&lt;p&gt;The core agent dialing logic lives in the AGI scripts. Locate the main agent call handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /usr/share/astguiclient/agi/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;VICIdial typically uses an AGI script (often named something like &lt;code&gt;call_agent.agi&lt;/code&gt; or routed via the extensions context &lt;code&gt;from-internal&lt;/code&gt;). The script must detect when a call destination is a bot or AI service, then route to the AI carrier instead of the standard PSTN carrier.&lt;/p&gt;

&lt;p&gt;Create a custom AGI script or modify the existing one. Here is a simplified example that checks the destination in the database and routes accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/perl&lt;/span&gt;
use DBI&lt;span class="p"&gt;;&lt;/span&gt;
use Asterisk::AGI&lt;span class="p"&gt;;&lt;/span&gt;
use strict&lt;span class="p"&gt;;&lt;/span&gt;

my &lt;span class="nv"&gt;$agi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; new Asterisk::AGI&lt;span class="p"&gt;;&lt;/span&gt;
my %hash &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$agi&lt;/span&gt;-&amp;gt;ReadParse&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

my &lt;span class="nv"&gt;$exten&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$hash&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'extension'&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
my &lt;span class="nv"&gt;$channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$hash&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'channel'&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
my &lt;span class="nv"&gt;$agent_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$hash&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;'callerid'&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;# Database connection (adjust DSN to your setup)&lt;/span&gt;
my &lt;span class="nv"&gt;$dbh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; DBI-&amp;gt;connect&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'dbi:mysql:asterisk:localhost'&lt;/span&gt;, &lt;span class="s1"&gt;'vicidial'&lt;/span&gt;, &lt;span class="s1"&gt;'vicidial_password'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;# Query the destination to see if it is an AI bot&lt;/span&gt;
my &lt;span class="nv"&gt;$sth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$dbh&lt;/span&gt;-&amp;gt;prepare&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;"SELECT bot_flag FROM vicidial_agent_pool WHERE agent_id = ? AND bot_flag = 'Y' LIMIT 1"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$sth&lt;/span&gt;-&amp;gt;execute&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$agent_id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
my &lt;span class="nv"&gt;$is_bot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$sth&lt;/span&gt;-&amp;gt;fetchrow_array&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$sth&lt;/span&gt;-&amp;gt;finish&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c"&gt;# Check if the dialed extension matches a bot carrier&lt;/span&gt;
my &lt;span class="nv"&gt;$bot_sth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$dbh&lt;/span&gt;-&amp;gt;prepare&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;"SELECT carrier_id FROM vicidial_carrier_log WHERE carrier_name LIKE '%AI%' OR carrier_name LIKE '%Bot%' LIMIT 1"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$bot_sth&lt;/span&gt;-&amp;gt;execute&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
my &lt;span class="nv"&gt;$ai_carrier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$bot_sth&lt;/span&gt;-&amp;gt;fetchrow_array&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$bot_sth&lt;/span&gt;-&amp;gt;finish&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$is_bot&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$exten&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ /^555/&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;  &lt;span class="c"&gt;# Example: 555xxxx is reserved for bots&lt;/span&gt;
    &lt;span class="nv"&gt;$agi&lt;/span&gt;-&amp;gt;exec&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Dial'&lt;/span&gt;, &lt;span class="s2"&gt;"SIP/&lt;/span&gt;&lt;span class="nv"&gt;$exten&lt;/span&gt;&lt;span class="se"&gt;\@&lt;/span&gt;&lt;span class="s2"&gt;AIVoiceOutbound,30,tT"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# Standard PSTN routing&lt;/span&gt;
    &lt;span class="nv"&gt;$agi&lt;/span&gt;-&amp;gt;exec&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Dial'&lt;/span&gt;, &lt;span class="s2"&gt;"SIP/&lt;/span&gt;&lt;span class="nv"&gt;$exten&lt;/span&gt;&lt;span class="se"&gt;\@&lt;/span&gt;&lt;span class="s2"&gt;StandardCarrier,30,tT"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$dbh&lt;/span&gt;-&amp;gt;disconnect&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;0&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script is illustrative. In production, integrate this logic into your existing AGI handler or use VICIdial's built-in call routing tables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring remote agent soft-phone settings
&lt;/h2&gt;

&lt;p&gt;Remote agents connect to VICIdial via SIP. The agent's soft-phone must be configured with the VICIdial SIP server credentials. Common soft-phones include Zoiper, Linphone, or MicroSIP.&lt;/p&gt;

&lt;p&gt;Example SIP configuration for a remote agent soft-phone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SIP Server: 192.168.1.100 (your VICIdial server)&lt;/li&gt;
&lt;li&gt;Port: 5060&lt;/li&gt;
&lt;li&gt;Username: agent_001&lt;/li&gt;
&lt;li&gt;Password: agent_password&lt;/li&gt;
&lt;li&gt;Display Name: Agent 001&lt;/li&gt;
&lt;li&gt;Codecs (in order): G.711u (ulaw), G.729, GSM&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Test the connection by placing a call from the agent soft-phone to an extension on the VICIdial system. In the Asterisk CLI, confirm the channel appears:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip show channels"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a channel like &lt;code&gt;SIP/agent_001-00000123&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ensuring codec compatibility between agent and AI platform
&lt;/h2&gt;

&lt;p&gt;Both the agent's SIP connection and the AI platform trunk must support at least one common codec. G.711u (ulaw) is the most widely supported but has higher bandwidth. G.729 requires a license but compresses better. GSM is lightweight but lower quality.&lt;/p&gt;

&lt;p&gt;Check what codecs the AI platform accepts. Most document this in their SIP trunk setup guide. In your sip-vicidial.conf for the AIVoiceOutbound peer, list codecs in order of preference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;allow&lt;/span&gt;=&lt;span class="n"&gt;ulaw&lt;/span&gt;,&lt;span class="n"&gt;alaw&lt;/span&gt;,&lt;span class="n"&gt;gsm&lt;/span&gt;,&lt;span class="n"&gt;g729&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, ensure the agent soft-phone supports these codecs. If a mismatch occurs, Asterisk will attempt transcoding on the fly, which increases CPU load and introduces latency. Force a common codec in the dial plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Dial&lt;/span&gt;(&lt;span class="n"&gt;SIP&lt;/span&gt;/${&lt;span class="n"&gt;EXTEN&lt;/span&gt;}@&lt;span class="n"&gt;AIVoiceOutbound&lt;/span&gt;/&lt;span class="n"&gt;ulaw&lt;/span&gt;,&lt;span class="m"&gt;45&lt;/span&gt;,&lt;span class="n"&gt;tT&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitor codec selection on active calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core show channels verbose"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for lines like &lt;code&gt;Format: ulaw (ulaw)&lt;/code&gt; or &lt;code&gt;Format: unknown&lt;/code&gt; (which indicates a codec issue).&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling RTP and jitter in WAN environments
&lt;/h2&gt;

&lt;p&gt;Remote agents connecting over public internet face variable latency and packet loss. VICIdial's Asterisk must be configured to handle this gracefully.&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;/etc/asterisk/rtp.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;general&lt;/span&gt;]
&lt;span class="n"&gt;rtpstart&lt;/span&gt;=&lt;span class="m"&gt;10000&lt;/span&gt;
&lt;span class="n"&gt;rtpend&lt;/span&gt;=&lt;span class="m"&gt;20000&lt;/span&gt;
&lt;span class="n"&gt;rtcpcheck&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;nat&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;icesupport&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;dtlscerti&lt;/span&gt;=/&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;asterisk&lt;/span&gt;/&lt;span class="n"&gt;keys&lt;/span&gt;/&lt;span class="n"&gt;asterisk&lt;/span&gt;.&lt;span class="n"&gt;crt&lt;/span&gt;
&lt;span class="n"&gt;dtlscertfile&lt;/span&gt;=/&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;asterisk&lt;/span&gt;/&lt;span class="n"&gt;keys&lt;/span&gt;/&lt;span class="n"&gt;asterisk&lt;/span&gt;.&lt;span class="n"&gt;key&lt;/span&gt;
&lt;span class="n"&gt;dtlssetup&lt;/span&gt;=&lt;span class="n"&gt;actpass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ICE (Interactive Connectivity Establishment) support helps agents behind NAT/firewalls find the best path. DTLS adds encryption and works with SRTP (Secure RTP).&lt;/p&gt;

&lt;p&gt;Increase the jitter buffer size for WAN calls. Edit &lt;code&gt;/etc/asterisk/asterisk.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;options&lt;/span&gt;]
&lt;span class="n"&gt;maxfiles&lt;/span&gt;=&lt;span class="m"&gt;131072&lt;/span&gt;
&lt;span class="n"&gt;debug&lt;/span&gt;=&lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in the SIP peer for remote agents, add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;RemoteAgent&lt;/span&gt;]
&lt;span class="n"&gt;type&lt;/span&gt;=&lt;span class="n"&gt;peer&lt;/span&gt;
&lt;span class="n"&gt;host&lt;/span&gt;=&lt;span class="n"&gt;dynamic&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt;=&lt;span class="n"&gt;ulaw&lt;/span&gt;,&lt;span class="n"&gt;alaw&lt;/span&gt;,&lt;span class="n"&gt;gsm&lt;/span&gt;,&lt;span class="n"&gt;g729&lt;/span&gt;
&lt;span class="n"&gt;disallow&lt;/span&gt;=&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="n"&gt;nat&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;qualify&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;qualify_timeout&lt;/span&gt;=&lt;span class="m"&gt;3&lt;/span&gt;
&lt;span class="n"&gt;qualify_freq&lt;/span&gt;=&lt;span class="m"&gt;30&lt;/span&gt;
&lt;span class="n"&gt;icesupport&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;directmedia&lt;/span&gt;=&lt;span class="n"&gt;no&lt;/span&gt;
&lt;span class="n"&gt;jbenable&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;jbimpl&lt;/span&gt;=&lt;span class="n"&gt;adaptive&lt;/span&gt;
&lt;span class="n"&gt;jbmaxsize&lt;/span&gt;=&lt;span class="m"&gt;200&lt;/span&gt;
&lt;span class="n"&gt;useclientcode&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The jitter buffer (jbenable=yes, jbimpl=adaptive) smooths out packet arrival timing. jbmaxsize=200 allows up to 200ms of buffering.&lt;/p&gt;

&lt;p&gt;Test latency from the agent machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ping &lt;span class="nt"&gt;-c&lt;/span&gt; 10 192.168.1.100
mtr &lt;span class="nt"&gt;-c&lt;/span&gt; 20 192.168.1.100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Latency should be under 100ms; above 150ms, VoIP quality degrades noticeably.&lt;/p&gt;

&lt;h2&gt;
  
  
  Storing and retrieving agent call context in the database
&lt;/h2&gt;

&lt;p&gt;When an agent initiates a call to an AI bot, context about the agent (ID, name, campaign) and the lead (phone, notes) must be passed to the AI system. This is often done via SIP headers or call recording metadata.&lt;/p&gt;

&lt;p&gt;VICIdial stores agent login and call state in the &lt;code&gt;vicidial_agent_log&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_agent_log&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'agent_001'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before routing a call to the AI platform, query the agent's current campaign and pass it as a custom SIP header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;SIP_HEADERS&lt;/span&gt;(&lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Agent&lt;/span&gt;-&lt;span class="n"&gt;Campaign&lt;/span&gt;)=${&lt;span class="n"&gt;CAMPAIGN&lt;/span&gt;})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;SIP_HEADERS&lt;/span&gt;(&lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Lead&lt;/span&gt;-&lt;span class="n"&gt;Phone&lt;/span&gt;)=${&lt;span class="n"&gt;EXTEN&lt;/span&gt;})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;SIP_HEADERS&lt;/span&gt;(&lt;span class="n"&gt;X&lt;/span&gt;-&lt;span class="n"&gt;Agent&lt;/span&gt;-&lt;span class="n"&gt;ID&lt;/span&gt;)=${&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;num&lt;/span&gt;)})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Dial&lt;/span&gt;(&lt;span class="n"&gt;SIP&lt;/span&gt;/${&lt;span class="n"&gt;EXTEN&lt;/span&gt;}@&lt;span class="n"&gt;AIVoiceOutbound&lt;/span&gt;,&lt;span class="m"&gt;45&lt;/span&gt;,&lt;span class="n"&gt;tT&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the AI platform side (if you control both endpoints), parse these headers to load agent context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recording calls between agent and AI voice platform
&lt;/h2&gt;

&lt;p&gt;By default, VICIdial records all calls to the recordings directory (typically &lt;code&gt;/var/spool/asterisk/monitor/&lt;/code&gt;). When a call is transferred to an AI platform, Asterisk bridges the channels, and the recording continues. However, if the bridge is not set to "mix audio," only one direction is captured.&lt;/p&gt;

&lt;p&gt;Ensure calls are recorded in mixed mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;MixMonitor&lt;/span&gt;(/&lt;span class="n"&gt;var&lt;/span&gt;/&lt;span class="n"&gt;spool&lt;/span&gt;/&lt;span class="n"&gt;asterisk&lt;/span&gt;/&lt;span class="n"&gt;monitor&lt;/span&gt;/${&lt;span class="n"&gt;UNIQUEID&lt;/span&gt;}.&lt;span class="n"&gt;wav&lt;/span&gt;,&lt;span class="n"&gt;ab&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Dial&lt;/span&gt;(&lt;span class="n"&gt;SIP&lt;/span&gt;/${&lt;span class="n"&gt;EXTEN&lt;/span&gt;}@&lt;span class="n"&gt;AIVoiceOutbound&lt;/span&gt;,&lt;span class="m"&gt;45&lt;/span&gt;,&lt;span class="n"&gt;tT&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ab&lt;/code&gt; flags mean: a = record agent side, b = record the other side.&lt;/p&gt;

&lt;p&gt;After the call, the file &lt;code&gt;/var/spool/asterisk/monitor/${UNIQUEID}.wav&lt;/code&gt; will contain both sides of the conversation. VICIdial's database log in &lt;code&gt;vicidial_log&lt;/code&gt; or &lt;code&gt;vicidial_closer_log&lt;/code&gt; will record the filename in the &lt;code&gt;recording_id&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;Query call recordings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recording_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;agent_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'agent_001'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Listen to the recording at &lt;code&gt;/var/spool/asterisk/monitor/recording_id.wav&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing the integration end-to-end
&lt;/h2&gt;

&lt;p&gt;Set up a test agent account and soft-phone. Log the agent into the VICIdial web screen at &lt;code&gt;/agc/vicidial.php&lt;/code&gt;. Place a test call to a number that maps to your AI platform (either via a bot flag in the database or a special 555xxxx extension).&lt;/p&gt;

&lt;p&gt;On the server, tail the Asterisk log:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/asterisk/messages
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitor call state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core show channels verbose"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the call, check:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Both agent and AI platform RTP streams are active (no "No RTP activity" warnings).&lt;/li&gt;
&lt;li&gt;Codec negotiation completes (look for "Format: ulaw" or similar).&lt;/li&gt;
&lt;li&gt;Call recording file is created and contains audio from both sides.&lt;/li&gt;
&lt;li&gt;After hangup, call record appears in vicidial_log with correct disposition.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Example successful log output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[2024-01-15 14:23:45] NOTICE[1234][C-0000a1b2]: app_dial.c:xxxx: Called SIP/agent_001@RemoteAgent
[2024-01-15 14:23:47] NOTICE[1234][C-0000a1b2]: app_dial.c:xxxx: SIP/agent_001@RemoteAgent is ringing
[2024-01-15 14:23:50] NOTICE[1234][C-0000a1b2]: app_dial.c:xxxx: SIP/agent_001@RemoteAgent answered call
[2024-01-15 14:23:50] NOTICE[1234][C-0000a1b2]: bridge.c:xxxx: Bridging SIP/agent_001@RemoteAgent with SIP/5551234567@AIVoiceOutbound
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the call fails to bridge, check for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"No route to host" or timeout: Network/firewall issue between VICIdial and AI platform.&lt;/li&gt;
&lt;li&gt;"Codec not compatible": Add a codec translation or force a common one.&lt;/li&gt;
&lt;li&gt;"Authentication failed": Verify carrier username and password in sip-vicidial.conf.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Agent hears silence or one-way audio during AI voice calls&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check codec negotiation by running &lt;code&gt;asterisk -rx "sip show channels"&lt;/code&gt; during an active call. If the format shown is "unknown" or mismatched between channels, add explicit codec forcing to the dial plan. Verify that the AI platform trunk supports the codec your agent's soft-phone negotiates. Run a SIP debug capture: &lt;code&gt;asterisk -rx "sip set debug on"&lt;/code&gt; and review SDP payloads in the logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Calls disconnect after 30 seconds&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is often a SIP timeout or lack of RTP activity. Edit the Dial() command to increase the timeout: &lt;code&gt;Dial(SIP/${EXTEN}@AIVoiceOutbound,120,tT)&lt;/code&gt; instead of the default 30 seconds. If RTP is not flowing, check that directmedia=no is set on the peer and that the agent and platform are using compatible RTP ports (rtpstart/rtpend in rtp.conf should not overlap with other services).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent's soft-phone does not register to VICIdial&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ensure the agent's SIP username and password match an entry in the VICIdial database (typically in a custom user table or in Asterisk's AstDB). Verify the agent soft-phone is pointing to the correct VICIdial server IP and port 5060. Check firewall rules on the VICIdial server: &lt;code&gt;sudo ufw allow 5060/udp&lt;/code&gt; or disable iptables temporarily to isolate the issue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recordings are one-sided or empty&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ensure MixMonitor is called before Dial() in the extensions context. Check that &lt;code&gt;/var/spool/asterisk/monitor/&lt;/code&gt; has write permissions for the asterisk user: &lt;code&gt;ls -la /var/spool/asterisk/monitor/&lt;/code&gt;. If the directory doesn't exist, create it: &lt;code&gt;sudo mkdir -p /var/spool/asterisk/monitor &amp;amp;&amp;amp; sudo chown asterisk:asterisk /var/spool/asterisk/monitor/&lt;/code&gt;. Verify that app_mixmonitor is loaded: &lt;code&gt;asterisk -rx "core show modules" | grep mixmonitor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High latency or jitter causing audio drops&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Run a continuous ping and mtr test to the AI platform endpoint to measure latency. If consistent latency is above 150ms, check network routing via traceroute. On the VICIdial side, increase the jitter buffer: set jbmaxsize=400 and jbimpl=adaptive in the SIP peer config. Reduce audio codec bandwidth by forcing G.729 (if licensed) or GSM instead of G.711u.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI platform returns authentication failure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Double-check the carrier_user and carrier_pass in the vicidial_carrier_log table. Many AI platforms issue SIP credentials that include a domain or realm; include those in the username field if necessary (e.g., &lt;code&gt;vicidial_user@platform.example.com&lt;/code&gt;). Test the credentials independently with a SIP client like Linphone to confirm they work before debugging the Asterisk integration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inbound calls from AI platform do not route to agent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Verify the [from-ai-voice] context exists in extensions-vicidial.conf and includes a macro or dialplan to route to your agent queue. Ensure the AI platform is configured to send inbound calls to the correct VICIdial server IP and extension. Check that the VICIdial firewall allows inbound SIP on port 5060 from the AI platform's public IP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can I use G.729 codec to save bandwidth for remote agents?
&lt;/h3&gt;

&lt;p&gt;Yes, but you must have a G.729 codec license installed on Asterisk. Purchase licenses from DIGIUM or a reseller and install them in /usr/lib/asterisk/modules/. After installation, add &lt;code&gt;allow=g729&lt;/code&gt; to the relevant SIP peer configs. Confirm the codec loads by running &lt;code&gt;asterisk -rx "core show modules" | grep g729&lt;/code&gt;. Note that transcoding between G.729 and the AI platform's codec (often G.711) increases CPU load; if you have many simultaneous calls, stick with G.711u unless bandwidth is severely constrained.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I pass agent custom data or notes to the AI voice bot?
&lt;/h3&gt;

&lt;p&gt;Store the data in the VICIdial lead or agent record, then retrieve it in the AGI script before dialing. Query the vicidial_list table for lead notes or the vicidial_agent_log table for agent notes. Pass the data as SIP headers (e.g., &lt;code&gt;SIP_HEADERS(X-Lead-Notes)=${NOTES}&lt;/code&gt;) or embed it in the dialed extension (e.g., &lt;code&gt;SIP/555${LEAD_ID}@AIVoiceOutbound&lt;/code&gt;). The AI platform must be configured to parse these headers or extension patterns on the inbound side.&lt;/p&gt;

&lt;h3&gt;
  
  
  What if the AI platform uses WebSocket instead of SIP?
&lt;/h3&gt;

&lt;p&gt;VICIdial and Asterisk 16/18 do not have native WebSocket support for calls. However, you can bridge a SIP call to a WebSocket endpoint using a gateway service like Jambonz or FreeSWITCH in between. Alternatively, use the AI platform's SIP trunk if available (most offer one). If WebSocket is your only option, consider deploying a dedicated SIP-to-WebSocket gateway on the same network and pointing the VICIdial trunk to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I monitor call quality metrics like MOS score during AI agent calls?
&lt;/h3&gt;

&lt;p&gt;Asterisk does not natively compute MOS (Mean Opinion Score). However, you can enable RTP statistics logging and use third-party tools like SIPp or QEMU to analyze packet loss and jitter. For production monitoring, integrate with a service like Twilio or Vonage that provides call quality dashboards, or deploy an open-source RTPFLOW analysis tool. Store CDR data in VICIdial's database and correlate it with Asterisk logs to identify patterns in call failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I failover to a second AI voice platform if the primary is unreachable?
&lt;/h3&gt;

&lt;p&gt;Yes. In the dial plan, use Dial()'s multiple target syntax to try the primary platform first, then fall back to a secondary. For example: &lt;code&gt;Dial(SIP/${EXTEN}@AIVoicePrimary,20,tT|SIP/${EXTEN}@AIVoiceSecondary,20,tT)&lt;/code&gt;. If the primary times out or is unreachable, Asterisk will attempt the secondary. Ensure both platforms are defined as separate peers in sip-vicidial.conf. Monitor each platform's uptime and update DNS or config entries if you swap primary and secondary roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Integration of AI voice agents with VICIdial remote agents requires correct SIP trunk provisioning for the AI platform, codec alignment between agent soft-phones and the voice platform, and RTP/jitter buffer tuning for WAN stability. Implement custom AGI logic to route calls from agents to the AI carrier based on bot flags or reserved extension ranges. Configure remote agent soft-phones with compatible codecs and NAT settings, enforce MixMonitor for call recording on both sides of the bridge, and test end-to-end with a real agent and soft-phone before deploying to production.&lt;/p&gt;

&lt;p&gt;Monitor SIP show peers to confirm trunk registration, verify call recordings contain both agent and AI audio, and check vicidial_log for call disposition after each test. Common failure points are codec mismatch (fix via allow= directives), one-sided audio (enable MixMonitor with ab flags), and connectivity issues (test mtr and firewall rules). Once a test call succeeds with full audio, appropriate codec negotiation, and recorded wav file, the integration is ready for live campaigns.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sip</category>
      <category>voip</category>
    </item>
    <item>
      <title>How to Manage ViciDial Lists: Deactivate, Reset &amp; Delete Leads</title>
      <dc:creator>Moisi Trungu</dc:creator>
      <pubDate>Thu, 15 Oct 2026 03:30:02 +0000</pubDate>
      <link>https://dev.to/moisi_trungu_31647b7ac300/how-to-manage-vicidial-lists-deactivate-reset-delete-leads-heo</link>
      <guid>https://dev.to/moisi_trungu_31647b7ac300/how-to-manage-vicidial-lists-deactivate-reset-delete-leads-heo</guid>
      <description>&lt;h1&gt;
  
  
  How to Manage ViciDial Lists: Deactivate, Reset &amp;amp; Delete Leads
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Remove, reset, or purge unwanted contacts from ViciDial lists without corrupting call logs or orphaning agent records.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;List management in ViciDial requires direct database access combined with understanding how leads link to call logs, agent activity, and callbacks. Deactivating a lead preserves history; resetting a lead clears its attempt count and status; deleting a lead removes it entirely but risks breaking referential integrity if done carelessly. This guide shows the exact SQL, CLI commands, and web interface steps to manage each operation safely.&lt;/p&gt;

&lt;p&gt;Tested on ViciDial 2.14 (SVN 3555+) and 2.13, Asterisk 16/18, MariaDB 10.5+, PHP 7.4+.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;SSH access to the ViciDial server or a machine with remote MySQL client installed.&lt;/li&gt;
&lt;li&gt;MySQL or MariaDB command-line client (mysql) or a GUI tool like DBeaver or phpMyAdmin.&lt;/li&gt;
&lt;li&gt;Database credentials with SELECT, UPDATE, DELETE on the asterisk database.&lt;/li&gt;
&lt;li&gt;Understanding of ViciDial list IDs (numeric identifiers for campaigns/lists).&lt;/li&gt;
&lt;li&gt;A backup of the asterisk database taken within the last 24 hours.&lt;/li&gt;
&lt;li&gt;Agent or admin access to the web interface at /vicidial/admin.php.&lt;/li&gt;
&lt;li&gt;Knowledge of which leads are tied to active callbacks or scheduled follow-ups.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding ViciDial lead status codes
&lt;/h2&gt;

&lt;p&gt;Before modifying leads, know what each status means in the vicidial_list table.&lt;/p&gt;

&lt;p&gt;The status field controls whether an agent can dial a lead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NEW&lt;/strong&gt;: Untouched, ready to dial.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ACTIVE&lt;/strong&gt;: Currently being worked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DEAD&lt;/strong&gt;: Permanently removed from dialing queue (do not call, duplicate, bad number).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;INBOUND&lt;/strong&gt;: Reserved for inbound transfers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QUEUE&lt;/strong&gt;: Waiting for agent availability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PAUSE&lt;/strong&gt;: Temporarily held.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Related flags in the same table:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Called&lt;/strong&gt;: 0 = never called, 1 = called at least once. Resetting this is what "resetting" a lead means.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phone&lt;/strong&gt;: The phone number string (E.164 format preferred: +1-555-0101).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lead_id&lt;/strong&gt;: Auto-incremented primary key, unique per list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;List_id&lt;/strong&gt;: Foreign key linking to vicidial_lists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The vicidial_log table records every call attempt. Deleting a lead does not automatically delete its logs, so historical call data remains for reporting and compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deactivating leads through the web interface
&lt;/h2&gt;

&lt;p&gt;The simplest method for small volumes (10-100 leads) is the ViciDial admin panel.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to Admin Leads (e.g., &lt;a href="http://your-vicidial-server/vicidial/admin.php" rel="noopener noreferrer"&gt;http://your-vicidial-server/vicidial/admin.php&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Select your List from the dropdown.&lt;/li&gt;
&lt;li&gt;Use the Search form to find the lead by phone, name, or ID.&lt;/li&gt;
&lt;li&gt;Click the lead row to open its detail page.&lt;/li&gt;
&lt;li&gt;Change Status to DEAD.&lt;/li&gt;
&lt;li&gt;Set "Do Not Call" flag if you want to block it from future list reloads.&lt;/li&gt;
&lt;li&gt;Save.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This marks the lead as DEAD in vicidial_list.status, preventing agents from dialing it. The lead remains in the database for audit purposes. Call logs stay intact.&lt;/p&gt;

&lt;p&gt;Use this method when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You are removing a single lead or a handful.&lt;/li&gt;
&lt;li&gt;You need a timestamp audit trail (ViciDial logs admin actions).&lt;/li&gt;
&lt;li&gt;You want agent feedback recorded (notes field).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For bulk operations, use the database directly (see below).&lt;/p&gt;

&lt;h2&gt;
  
  
  Deactivating leads in bulk with SQL
&lt;/h2&gt;

&lt;p&gt;To deactivate 100+ leads at once, use direct SQL. This is 100 times faster than the web interface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Identify the list ID and lead range.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;list_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_lists&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'Campaign_Q4%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the list_id (e.g., 101).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Deactivate leads by phone number pattern.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DEAD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;called&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; 
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'555%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This marks all leads in list 101 with phone numbers starting in 555 as DEAD. The called = 1 ensures they appear in reporting as "attempted".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Verify the change.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; 
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;count(*)  status
-------  -------
  4521   NEW
   512   DEAD
   189   PAUSE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common deactivation scenarios:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Deactivate all leads in a list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DEAD'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deactivate leads with invalid phone numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DEAD'&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deactivate leads marked as duplicate in notes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DEAD'&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%duplicate%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deactivate leads not called in 30 days:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'DEAD'&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; 
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;last_local_call_time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After any bulk update, always run the verification query to confirm the count.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resetting leads (clearing attempts and status)
&lt;/h2&gt;

&lt;p&gt;Resetting a lead removes its call history and attempt counter, returning it to NEW status. This re-queues it for dialing without losing the contact record itself.&lt;/p&gt;

&lt;p&gt;Use reset when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An agent dialed a number by mistake (robocall or wrong extension).&lt;/li&gt;
&lt;li&gt;A lead was marked DEAD but is actually reachable (bad skip trace).&lt;/li&gt;
&lt;li&gt;You want to retry a lead that failed several times.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Reset a single lead by ID:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'NEW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;called&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4521&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reset all leads in a list that are currently PAUSE or DEAD:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'NEW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;called&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PAUSE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'DEAD'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Reset leads that have not been called in the last 60 days:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'NEW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;called&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; 
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_call_time&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;last_call_time&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not reset the entire active_in_list or priority fields; those control dialing order and should remain intact.&lt;/p&gt;

&lt;p&gt;Resetting does not delete call logs. The lead's history in vicidial_log remains for compliance. You are only clearing the queuing state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deleting leads permanently
&lt;/h2&gt;

&lt;p&gt;Permanent deletion removes a lead entirely from the vicidial_list table. Use this only when you are certain the lead should never appear in reports or callbacks.&lt;/p&gt;

&lt;p&gt;Risks of deleting:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Orphaned call logs: A vicidial_log record pointing to a deleted lead_id cannot be joined back.&lt;/li&gt;
&lt;li&gt;Broken callbacks: If a callback is scheduled for a deleted lead_id, it will fail.&lt;/li&gt;
&lt;li&gt;Compliance issues: You lose the record that a contact was in your system.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For these reasons, deactivation (DEAD status) is safer than deletion in most cases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete a single lead (verify first):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Check for callbacks&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;callback_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_date&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4521&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- If none, delete&lt;/span&gt;
&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4521&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Delete leads that were never called:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; 
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;called&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; 
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'NEW'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Delete duplicate phone numbers (keep the first occurrence):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; 
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; 
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;phone&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before running any DELETE, always take a backup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysqldump &lt;span class="nt"&gt;-u&lt;/span&gt; asterisk &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk vicidial_list &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/vicidial_list_backup_&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the DELETE in a transaction so you can roll back if needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;START&lt;/span&gt; &lt;span class="n"&gt;TRANSACTION&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;called&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'NEW'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Check affected rows&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ROW_COUNT&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;-- If correct, commit&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- If wrong, ROLLBACK;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Purging call logs for deleted leads
&lt;/h2&gt;

&lt;p&gt;If you delete leads, their call logs remain in vicidial_log. To clean them up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This removes any orphaned log entries. Run this once per month if you delete leads regularly.&lt;/p&gt;

&lt;p&gt;Check how many rows will be affected first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a large system (millions of logs), this query can take minutes. Use it during low-traffic windows (2-4 AM).&lt;/p&gt;

&lt;h2&gt;
  
  
  Resetting call attempt counters
&lt;/h2&gt;

&lt;p&gt;ViciDial tracks call attempts per lead in the vicidial_log table. Some lists use custom attempt limits (e.g., dial 5 times, then move to DEAD). If you want to allow more attempts without fully resetting the lead, clear the attempt count.&lt;/p&gt;

&lt;p&gt;The vicidial_log.lead_id and vicidial_log.list_id combination forms the basis for attempt counting. To allow a lead to be dialed again without losing history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;called&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; 
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This resets the called flag for leads called in the last 7 days, letting them dial again. The call logs remain for reporting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bulk importing and list resets
&lt;/h2&gt;

&lt;p&gt;When you import a new lead list via the web interface (Admin Leads, Upload), ViciDial assigns new lead_ids and calculates the next_state. If you want to reload the same list (e.g., after adding new fields), you must first reset or delete the old list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;To reload a list:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Note the list_id.&lt;/li&gt;
&lt;li&gt;Delete all leads in that list: &lt;code&gt;DELETE FROM vicidial_list WHERE list_id = 101;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Re-import the list via the web interface.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Alternatively, use the command-line script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/share/astguiclient/ADMIN_manual_list_reset.pl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--list-id&lt;/span&gt; 101 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--campaign&lt;/span&gt; Outbound_Q4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script safely resets the list, clearing all leads and resetting attempt counts. Check the script logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/astguiclient/ADMIN_manual_list_reset.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the CLI for list diagnostics
&lt;/h2&gt;

&lt;p&gt;Query the ViciDial database from the command line without entering MySQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; asterisk &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"SELECT lead_id, phone, status, called FROM vicidial_list WHERE list_id = 101 LIMIT 10;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Get a count by status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; asterisk &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"SELECT status, count(*) as count FROM vicidial_list WHERE list_id = 101 GROUP BY status;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find leads with callback scheduled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; asterisk &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"SELECT lead_id, callback_date, callback_notes FROM vicidial_callbacks &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
   WHERE callback_date &amp;gt; NOW() ORDER BY callback_date LIMIT 20;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check for active agents on a list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; asterisk &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"SELECT user, status, lead_id FROM vicidial_agent_status &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
   WHERE campaign = 'Outbound_Q4' AND status != 'OFFLINE';"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect to the Asterisk console to see if agents are paused or active:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"vicidial show agents"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hangup an agent's call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"channel request hangup SIP/2001"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Problem: Leads won't queue after resetting.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check if the list itself is active:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;campaign&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_lists&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If active = 0, the list is paused. Set active = 1 to resume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem: Deleted leads still appear in agent screen.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Agents may have cached the lead list in browser memory. Have them log out and log back in, or clear the browser cache.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem: Call logs show lead_id that no longer exists.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is normal if you deleted leads after calls. To prevent orphaned logs in the future, use DEAD status instead of DELETE.&lt;/p&gt;

&lt;p&gt;To see orphaned leads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt; 
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; &lt;span class="n"&gt;vl2&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vl2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;vl2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Problem: Bulk deactivation query timed out.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The vicidial_list table may lack indexes on frequently filtered columns. Add an index:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_list_status&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;list_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then retry the query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem: Web interface is slow after bulk update.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;ViciDial caches list state in memory. Restart the astguiclient services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl restart astguiclient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait 30 seconds for caches to rebuild.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem: Attempting to delete a lead returns foreign key constraint error.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A callback, inbound transfer, or agent note references this lead. Find and remove those references first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4521&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_inbound_anicode&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4521&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_agent_notes&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4521&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete or reassign those rows, then try deleting the lead again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why does ViciDial mark calls as DEAD automatically?
&lt;/h3&gt;

&lt;p&gt;ViciDial can be configured to auto-mark a lead as DEAD after a certain number of failed attempts (no answer, wrong number, answering machine). This is set in the campaign's dialer settings. If you want to disable auto-DEAD, go to Admin Campaigns, select your campaign, and set "Max attempts" to a high number (e.g., 999) or use a custom status like PAUSE instead. Leads marked DEAD manually will not be re-queued unless you reset them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I export a list of deactivated leads?
&lt;/h3&gt;

&lt;p&gt;Yes. Use the web interface (Admin Leads, Export) to download the list as CSV, or run a MySQL query and pipe it to a file: &lt;code&gt;mysql -u asterisk -p asterisk -e "SELECT * FROM vicidial_list WHERE list_id = 101 AND status = 'DEAD';" &amp;gt; /tmp/deactivated_leads.txt&lt;/code&gt;. The CSV export includes all fields and can be reimported into another system for compliance records.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happens to agent statistics if I delete a lead?
&lt;/h3&gt;

&lt;p&gt;Deleting a lead does not erase the agent's call history or stats. The vicidial_closer_log records agent performance (talk time, dials, disposition). If you delete the lead after the call, the log entry remains but the agent can no longer view the lead's details in the agent screen. For this reason, use DEAD status instead of deletion if you want to preserve agent accountability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I restore a deleted lead from backup?
&lt;/h3&gt;

&lt;p&gt;Yes, but only if you have a database backup taken before the deletion. Restore the backup to a new database, extract the deleted lead row, and insert it into the production database. ViciDial does not have a trash/undelete feature for individual leads. Always backup before bulk delete operations: &lt;code&gt;mysqldump -u asterisk -p asterisk vicidial_list &amp;gt; /tmp/backup.sql&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I reset leads without stopping active dialing?
&lt;/h3&gt;

&lt;p&gt;Use the database directly (SQL UPDATE statements). Web interface resets may lock the list temporarily. When updating with SQL, the change takes effect immediately without stopping agents. However, agents who are already dialing a lead will not see the reset until they finish the call and the system refreshes the lead state (usually within 2-3 seconds).&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;You can now deactivate, reset, or delete leads in ViciDial using three approaches: the web interface for single leads and auditing, SQL UPDATE for bulk deactivation, and SQL DELETE for permanent removal (use sparingly). Deactivation (DEAD status) is the safest choice in production because it preserves call logs and agent accountability while removing leads from the dialing queue. Resetting clears the attempt counter and status, re-queuing a lead without losing history. Deleting is permanent and risks breaking callbacks and orphaning logs.&lt;/p&gt;

&lt;p&gt;Always back up the database before bulk operations. Use START TRANSACTION and ROLLBACK if a query affects more rows than expected. After any bulk change, verify counts with SELECT queries and test with a single agent before resuming full-scale dialing. Run orphan log cleanup (DELETE from vicidial_log WHERE lead_id NOT IN...) monthly if you delete leads regularly, and add database indexes if bulk updates time out on large lists (100k+ leads).&lt;/p&gt;

&lt;p&gt;Next steps: review the dialer campaign settings to confirm max-attempt rules are aligned with your deactivation policy, set up a monthly backup cron job if you have not already, and test a reset on a single lead during a quiet period to verify behavior matches your expectations.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sip</category>
      <category>voip</category>
    </item>
    <item>
      <title>Detecting Inbound Dead Air: Answered Calls Where Asterisk Never Accepts Carrier RTP</title>
      <dc:creator>Moisi Trungu</dc:creator>
      <pubDate>Wed, 14 Oct 2026 03:30:02 +0000</pubDate>
      <link>https://dev.to/moisi_trungu_31647b7ac300/detecting-inbound-dead-air-answered-calls-where-asterisk-never-accepts-carrier-rtp-5ckn</link>
      <guid>https://dev.to/moisi_trungu_31647b7ac300/detecting-inbound-dead-air-answered-calls-where-asterisk-never-accepts-carrier-rtp-5ckn</guid>
      <description>&lt;h1&gt;
  
  
  Detecting Inbound Dead Air: Answered Calls Where Asterisk Never Accepts Carrier RTP
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Some inbound calls answer normally at the SIP level, but the PBX never accepts a single RTP packet from the carrier. Asterisk already logs the evidence: a Strict RTP "learning" line that never turns into a lock or a source switch. Here's how I turned that into an alert, corroborated it with Homer RTCP, avoided the early-media trap, and used one SIP header to decide where to send the ticket.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tested on: Asterisk 18 (ViciDial build, chan_sip, &lt;code&gt;strictrtp=seqno&lt;/code&gt;, ConfBridge/softmix agent bridges), Loki, Homer with heplify-server on PostgreSQL 16. Check your Asterisk version's &lt;code&gt;res_rtp_asterisk.c&lt;/code&gt; for the exact log wording.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What does inbound dead air look like?
&lt;/h2&gt;

&lt;p&gt;The agent answers and hears nothing. SIP looks normal (&lt;code&gt;INVITE&lt;/code&gt;, &lt;code&gt;183&lt;/code&gt;, &lt;code&gt;200 OK&lt;/code&gt;, &lt;code&gt;ACK&lt;/code&gt;, &lt;code&gt;BYE&lt;/code&gt;), the CDR shows a short answered call, and the agent gets blamed. The first one I caught sat in the agent's bridge for about 6 seconds. Asterisk's log showed the carrier's media address set from SDP, then nothing: no packet from that stream was ever accepted.&lt;/p&gt;

&lt;p&gt;Then it stopped being a one-off. One morning, 26 answered inbound calls in 44 minutes had no carrier RTP accepted at the PBX, across three inbound trunks: 26 of the 125 calls on those trunks that reached an agent bridge, about one in five. The previous day's 12 business hours had 1,330 bridged calls on the same trunks and none like this. What this shows is that no carrier RTP was &lt;em&gt;accepted at the PBX&lt;/em&gt;; assigning blame needs packet captures or carrier evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Asterisk log lines show whether RTP arrived?
&lt;/h2&gt;

&lt;p&gt;With strict RTP enabled (&lt;code&gt;strictrtp=yes&lt;/code&gt;, the default, or &lt;code&gt;seqno&lt;/code&gt;), &lt;code&gt;res_rtp_asterisk.c&lt;/code&gt; logs the source-learning state of every RTP stream at verbose level 4. Real lines from my server, with addresses, IDs and dates replaced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[15:33:36] VERBOSE[17871][C-0001a2b3] res_rtp_asterisk.c: 0x7f7f... -- Strict RTP learning after remote address set to: 198.51.100.23:45892
[13:42:40] VERBOSE[3563][C-0001a0f0] res_rtp_asterisk.c: 0x7f7e... -- Strict RTP learning complete - Locking on source address 203.0.113.10:10256
[13:43:01] VERBOSE[6158][C-0001a0f3] res_rtp_asterisk.c: 0x7f7f... -- Strict RTP switching to RTP target address 203.0.113.20:30818 as source
[13:43:01] VERBOSE[6158][C-0001a0f3] res_rtp_asterisk.c: 0x7f7e... -- Strict RTP switching source address to 192.0.2.108:41882
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What matters is &lt;em&gt;where in the source&lt;/em&gt; each one is printed (Asterisk 18):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"learning after remote address set to"&lt;/strong&gt; comes from &lt;code&gt;ast_rtp_remote_address_set()&lt;/code&gt;. It fires when the far end's address is set from SDP. No packet is involved. Call it the &lt;strong&gt;open&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Locking on source address"&lt;/strong&gt;, &lt;strong&gt;"switching to RTP target address ... as source"&lt;/strong&gt; and &lt;strong&gt;"switching source address to"&lt;/strong&gt; are all printed in &lt;code&gt;ast_rtp_read()&lt;/code&gt;, the incoming-packet path. Each one means a packet arrived and Asterisk accepted that source. Call any of them a &lt;strong&gt;close&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A missing close doesn't mean zero packets hit the NIC. Strict RTP silently drops packets (debug level only) while it qualifies a &lt;em&gt;different&lt;/em&gt; source address, which takes 4 sequential packets by default. So an open with no close means &lt;strong&gt;Asterisk never accepted a stream for that call&lt;/strong&gt;. A real 50-packet-per-second stream bridged for several seconds should get through probation from any address, so an unpaired open is a strong candidate, but only a candidate.&lt;/p&gt;

&lt;p&gt;Also, SDP says where the carrier wants to &lt;em&gt;receive&lt;/em&gt; media, not where it sends from. A different source shows up as "switching source address to ", so the code pairs any close of the same stream within the carrier's ranges. A source outside your map becomes a false candidate that RTCP refutes.&lt;/p&gt;

&lt;p&gt;Prerequisites: &lt;code&gt;messages&lt;/code&gt; must log &lt;code&gt;verbose&lt;/code&gt; at level 4 or higher, and you must match all three close forms. Over about four months my &lt;code&gt;messages&lt;/code&gt; file had 414,530 opens, 369,649 "Locking", 234,563 "switching to RTP target" and 101,985 "switching source address" lines. Match only "Locking" and you'll flag healthy calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I tell an answered inbound call from a ring that went nowhere?
&lt;/h2&gt;

&lt;p&gt;Unanswered calls also leave unpaired opens, since many carriers send nothing before the answer: 16 of 42 in a two-hour window around the incident. So for each unpaired open, read the lines with the same &lt;code&gt;C-&lt;/code&gt; ID and check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bridge evidence.&lt;/strong&gt; In my ViciDial build the customer channel goes into a ConfBridge softmix conference: &lt;code&gt;Channel SIP/&amp;lt;trunk&amp;gt;-xxxx joined 'softmix' base-bridge&lt;/code&gt;. Ring-group overflow via &lt;code&gt;Dial()&lt;/code&gt; logs &lt;code&gt;app_dial.c: SIP/... answered&lt;/code&gt; instead. The first such line is the &lt;strong&gt;join&lt;/strong&gt;; &lt;code&gt;left 'softmix'&lt;/code&gt; is the &lt;strong&gt;leave&lt;/strong&gt;. MeetMe-based ViciDial setups log different lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direction.&lt;/strong&gt; The context must name the inbound trunk's channel and inbound dialplan context. With one peer name for both directions, a silent outbound leg looks identical at the RTP layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DID and caller ID&lt;/strong&gt;, for the alert. &lt;code&gt;MixMonitor&lt;/code&gt; and my dialplan's &lt;code&gt;Phone call from ...&lt;/code&gt; line both run before the &lt;code&gt;200 OK&lt;/code&gt;, so neither proves an answer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the first incident, filtered to its &lt;code&gt;C-&lt;/code&gt; ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;15:33:36  Strict RTP learning after remote address set to: 198.51.100.23:45892
15:33:36  Executing [process@trunkinbound:10] Verbose("SIP/carrier_in-...", "Phone call from ...")
15:33:40  Executing [...] ConfBridge("SIP/carrier_in-...", "...,vici_agent_bridge,vici_customer_user")
15:33:40  Channel SIP/carrier_in-... joined 'softmix' base-bridge &amp;lt;...&amp;gt;
15:33:46  Channel SIP/carrier_in-... left 'softmix' base-bridge &amp;lt;...&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An open, a join, a leave six seconds later, no close.&lt;/p&gt;

&lt;p&gt;This covers calls that &lt;strong&gt;reached a bridge&lt;/strong&gt;. In the incident window one more call never locked: the PBX answered it into the queue and the carrier hung up 7 seconds later, before any agent. If queue dead air matters to you, anchor on the PBX's &lt;code&gt;200 OK&lt;/code&gt; instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  How long should I wait for a close?
&lt;/h2&gt;

&lt;p&gt;Don't measure the hold from the open, which happens when the call &lt;em&gt;arrives&lt;/em&gt;: ring-group overflow calls ring a long time (48 s in a 12-hour sample).&lt;/p&gt;

&lt;p&gt;Anchor on the join instead. From the raw log file over 12 business hours: 1,330 bridged inbound calls on the three trunks, every one closed, median close in the same second as the join, latest 2 s after it. These time the logged transition, not packet arrival, at one-second resolution. During the incident the latest healthy close was 5 s after the join.&lt;/p&gt;

&lt;p&gt;My rule: no close for the call by &lt;strong&gt;join + 20 s&lt;/strong&gt;. That's several times the worst case, and the candidate is ready well under a minute after the agent picks up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the detector code look like?
&lt;/h2&gt;

&lt;p&gt;My watcher reads the logs from Loki, but tailing &lt;code&gt;messages&lt;/code&gt; works the same. This is the core matching logic, simplified from my production script with documentation-range addresses. It runs as shown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="c1"&gt;# One entry per inbound door. The SAME matcher is used for opens and closes.
&lt;/span&gt;&lt;span class="n"&gt;CARRIERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;carrier_a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;203\.0\.113\.\d+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;198\.51\.100\.\d+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SIP/carrier_a_in_&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;carrier_b_nl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192\.0\.2\.190&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SIP/carrier_b_nl-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;carrier_b_de&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192\.0\.2\.163&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chan&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SIP/carrier_b_de-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;CARRIERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;re&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(?:%s):\d+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;

&lt;span class="c1"&gt;# [C-call tag] ... res_rtp_asterisk.c: 0x&amp;lt;RTP instance&amp;gt; -- Strict RTP &amp;lt;event&amp;gt;
&lt;/span&gt;&lt;span class="n"&gt;RE_STRICT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\[(C-[0-9a-f]+)\] res_rtp_asterisk\.c: (0x[0-9a-f]+) -- Strict RTP (.*)$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;RE_OPEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learning after remote address set to: (\S+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;RE_CLOSE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;learning complete - Locking on source address (\S+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                      &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|switching to RTP target address (\S+) as source&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                      &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|switching source address to (\S+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;media_carrier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip_port&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;CARRIERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;re&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;fullmatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip_port&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;lines: [(ts_ns, text)] for ONE PBX, complete for the window.
    Returns (opens, closes, multi).
    opens: the FIRST carrier-facing stream of each call (the inbound leg&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s).
    closes: {(call_tag, rtp_instance): [(ts_ns, seq)]}, carrier-range only.
    multi: call tags that opened more than one carrier-facing RTP instance
    (e.g. an overflow dialled out through a carrier). Flag those for review.
    seq is the position after a stable sort by time, so same-second lines
    keep their order.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;ordered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="n"&gt;opens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;instances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ordered&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RE_STRICT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RE_OPEN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;media_carrier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cid&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="c1"&gt;# later re-opens (re-INVITE) and other legs: not covered
&lt;/span&gt;                &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts_ns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ip_port&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trunk&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;media_carrier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt;
                &lt;span class="n"&gt;opens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RE_CLOSE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;ip_port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;# any address in a carrier range, so a learned source change pairs
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;media_carrier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip_port&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="n"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inst&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;multi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cid&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;opens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multi&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;first_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;upto_ns&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;First close of the SAME call and SAME RTP instance after the open and
    no later than upto_ns. Another call reusing the port, or another leg of
    this call, can&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t pair it.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cid&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inst&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts_ns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;seq&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;upto_ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every Strict RTP line carries the call's &lt;code&gt;C-&lt;/code&gt; tag and the RTP instance pointer (&lt;code&gt;0x...&lt;/code&gt;), so closes are keyed by both, not by port. Carriers reuse ports, so a match keyed on port can let a later call's lock "close" a dead one. Keying on the instance stops another leg of the same call from closing it too, for example an overflow dialled back out through a carrier. Calls with more than one carrier-facing instance come back in &lt;code&gt;multi&lt;/code&gt;, so you can review them; my two days of raw logs had none. Only carrier-range closes count, so a ring-group phone's stream doesn't. The input must be complete and in time order; the stable sort keeps same-second lines in file order.&lt;/p&gt;

&lt;p&gt;A Loki query that feeds it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{server="pbx", job="asterisk"} |= "Strict RTP" |~ "203\\.0\\.113\\.\\d+|198\\.51\\.100\\.\\d+|192\\.0\\.2\\.190|192\\.0\\.2\\.163"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;LogQL literals use Go escaping, so a regex &lt;code&gt;\.&lt;/code&gt; is written &lt;code&gt;\\.&lt;/code&gt;. &lt;code&gt;query_range&lt;/code&gt; returns newest-first by default and silently stops at &lt;code&gt;limit&lt;/code&gt;, so use &lt;code&gt;direction=forward&lt;/code&gt;, split any window that hits the limit until none does, and merge before parsing. Then check the pipeline against the source: for one 12-hour window, two Loki runs gave 2,121 and 2,016 opens where the raw &lt;code&gt;messages&lt;/code&gt; file had 2,284. All numbers here come from the raw file.&lt;/p&gt;

&lt;p&gt;Each pass keeps as a &lt;strong&gt;candidate&lt;/strong&gt; any bridged inbound call with no close by &lt;code&gt;join + 20 s&lt;/code&gt; plus an ingest margin, and sends it to Homer before posting.&lt;/p&gt;

&lt;p&gt;Opens and closes must use &lt;strong&gt;the same matcher&lt;/strong&gt;: an alias of mine that matched one carrier's two POP ranges for opens but one for closes made every healthy call on the other POP a false candidate.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can Homer RTCP add?
&lt;/h2&gt;

&lt;p&gt;Your own RTCP &lt;em&gt;corroborates&lt;/em&gt; the log signal; it comes from the same reception state, so it isn't independent proof. The carrier's RTCP, where it sends any, is the closest thing to independent evidence short of packet captures at both ends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your side.&lt;/strong&gt; In Asterisk 18 a report carries a reception block once the far end's SSRC is recorded (&lt;code&gt;themssrc_valid&lt;/code&gt;), which happens after a packet passes strict RTP and then stays set. RFC 3550 only requires blocks for sources heard since the previous report, so another stack may drop the block between bursts; check yours. heplify stores RTCP as JSON in &lt;code&gt;hep_proto_5_default&lt;/code&gt;. In 128,786 reports around the incident, &lt;code&gt;report_blocks&lt;/code&gt; was a non-empty array or &lt;code&gt;null&lt;/code&gt;, never empty, missing or unparseable, but the SQL below still treats those as unclear rather than "nothing received".&lt;/p&gt;

&lt;p&gt;Here are my side's reports for one dead call, in seconds relative to the &lt;code&gt;200 OK&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T-0.6  {"sender_information":{"packets":250,...},"report_blocks":null}   &amp;lt;- before the answer
T+4.4  {"sender_information":{"packets":491,...},"report_blocks":null}
T+9.4  {"sender_information":{"packets":741,...},"report_blocks":null}
T+14.4 {"sender_information":{"packets":991,...},"report_blocks":null}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We sent 50 packets a second; no far-end SSRC was ever recorded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The carrier's side.&lt;/strong&gt; Under RFC 3550, a participant that has sent RTP since its last report sends a Sender Report; otherwise a Receiver Report. heplify's decoder only fills &lt;code&gt;sender_information&lt;/code&gt; for a Sender Report, so a Receiver Report shows zeros. The stored &lt;code&gt;type&lt;/code&gt; is useless here (on compound packets it's the last sub-packet, 202 on most of my rows), so I test for a non-zero NTP timestamp; in one hour of my data it always coincided with a non-zero packet count. In that dead call every carrier report had a zero NTP timestamp, and a report block for &lt;em&gt;our&lt;/em&gt; stream with the sequence number climbing about 200 every 4 s.&lt;/p&gt;

&lt;p&gt;Over one hour on that carrier's trunks, all 85 calls where our side reported reception got real Sender Reports from the carrier (1,680 of 1,723 carrier reports had a sender timestamp). The dead calls got Receiver Reports only. That corroborates the log signal: the carrier's RTCP stack didn't consider itself a sender at its SBC. Corroboration, not proof, and silent about further upstream. The other carrier's relay sent no RTCP my capture saw.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is RTCP from before the answer meaningless?
&lt;/h2&gt;

&lt;p&gt;The first report above was emitted &lt;em&gt;before&lt;/em&gt; the &lt;code&gt;200 OK&lt;/code&gt;. My PBX replies with &lt;code&gt;183 Session Progress&lt;/code&gt; with SDP and starts early media (ringback) at once, then sends the final &lt;code&gt;200 OK&lt;/code&gt; on answer. So it emits RTCP during the ring, when a healthy carrier usually isn't sending yet.&lt;/p&gt;

&lt;p&gt;In the hour before the incident, all 148 bridged calls with my RTCP had our &lt;code&gt;183&lt;/code&gt; before the &lt;code&gt;200 OK&lt;/code&gt;. 48 had a report before the answer, and &lt;strong&gt;all 48 pre-answer reports had no receiver block&lt;/strong&gt;. From 2 s after the answer it flipped: 2,420 reports, all with a block, and no healthy call where they were all empty.&lt;/p&gt;

&lt;p&gt;In the incident window a call torn down 3.7 s after the answer had one pre-answer report from my side, with no block. On RTCP alone it looks dead, but the Asterisk log shows it locked at the answer.&lt;/p&gt;

&lt;p&gt;The rule I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Refuted:&lt;/strong&gt; any report from your side, at any time, has a non-empty block. Asterisk accepted a packet. Don't post.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirmed:&lt;/strong&gt; at least one report from your side &lt;strong&gt;2 s or more after the &lt;code&gt;200 OK&lt;/code&gt;&lt;/strong&gt;, and none of your reports for the call, read through the end of the call, has a block.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown:&lt;/strong&gt; no post-answer report yet (very short calls, capture lag). Re-check next pass, and say so if it never settles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All 26 incident calls came back confirmed: 1 to 5 post-answer reports each from my side, read 20 minutes past the answer (so through the end of every call), none with a block, and no Sender Report from either carrier.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I find the right Homer dialog for a log line?
&lt;/h2&gt;

&lt;p&gt;Homer doesn't know the &lt;code&gt;C-&lt;/code&gt; ID, but the carrier's SDP has the media &lt;code&gt;ip:port&lt;/code&gt;. It's one statement, so no result from a previous call can leak in. Run it with &lt;code&gt;psql -v pbx_ip=... -v sig_ips=a,b -v media_ip=... -v media_port=... -v open_ts='YYYY-MM-DD HH:MM:SS+00' -f&lt;/code&gt;. RTCP is joined to the dialog by &lt;code&gt;sid&lt;/code&gt;, which only works if your heplify correlates RTCP to the SIP Call-ID (mine does; check a known call first). My &lt;code&gt;raw&lt;/code&gt; columns are &lt;code&gt;bytea&lt;/code&gt;; if yours are &lt;code&gt;text&lt;/code&gt;, drop the &lt;code&gt;convert_from(...)&lt;/code&gt; wrappers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- One statement, no carried-over variables. Inputs: pbx_ip, sig_ips (comma list),&lt;/span&gt;
&lt;span class="c1"&gt;-- media_ip, media_port, open_ts. Output: one row with a verdict.&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;   &lt;span class="c1"&gt;-- carrier INVITEs whose SDP carries the open's media endpoint&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;convert_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'UTF8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'(?i)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;x-oid:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s1"&gt;*([^&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s1"&gt;]+)'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;x_oid&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;hep_proto_1_call&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_date&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'open_ts'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'90 seconds'&lt;/span&gt;
                          &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'open_ts'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'3 seconds'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;protocol_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'dstIp'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'pbx_ip'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;protocol_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'srcIp'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ANY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string_to_array&lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="s1"&gt;'sig_ips'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;convert_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'UTF8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'INVITE %'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;convert_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'UTF8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;c=IN IP4 '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(:&lt;/span&gt;&lt;span class="s1"&gt;'media_ip'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\r&lt;/span&gt;&lt;span class="s1"&gt;?&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;convert_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'UTF8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;m=audio '&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'media_port'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;       &lt;span class="c1"&gt;-- exactly one dialog, or nothing&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_oid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;x_oid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;sid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;       &lt;span class="c1"&gt;-- first 200 to an INVITE, sent by the PBX&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;hep_proto_1_call&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;
               &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;
                 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_date&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'open_ts'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'90 seconds'&lt;/span&gt;
                                       &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'open_ts'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'10 minutes'&lt;/span&gt;
                 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;protocol_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'srcIp'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'pbx_ip'&lt;/span&gt;
                 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;convert_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'UTF8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s1"&gt;'^SIP/2&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s1"&gt;0 200 '&lt;/span&gt;
                 &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;convert_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'UTF8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;~*&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;cseq:&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s1"&gt;*[0-9]+&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="s1"&gt;+invite'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;t200&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;       &lt;span class="c1"&gt;-- every RTCP report of that dialog, classified&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;protocol_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'srcIp'&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;                                &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'unparsed'&lt;/span&gt;
              &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'report_blocks'&lt;/span&gt;                  &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'missing'&lt;/span&gt;
              &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;jsonb_typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'report_blocks'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'null'&lt;/span&gt;  &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'none'&lt;/span&gt;
              &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;jsonb_typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'report_blocks'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'array'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'malformed'&lt;/span&gt;
              &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;jsonb_array_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'report_blocks'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'empty'&lt;/span&gt;
              &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'blocks'&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;hep_proto_5_default&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sid&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_date&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t200&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'2 minutes'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t200&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'20 minutes'&lt;/span&gt;
  &lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;LATERAL&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;convert_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'UTF8'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="n"&gt;JSON&lt;/span&gt;
                                  &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;convert_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'UTF8'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t200&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'unparsed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'missing'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'malformed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'empty'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;unclear&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'pbx_ip'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'blocks'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pbx_with_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'pbx_ip'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;create_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;t200&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'2 seconds'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pbx_post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'pbx_ip'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;rb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'none'&lt;/span&gt;
                     &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;create_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;t200&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'2 seconds'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;pbx_post_none&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;FILTER&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;'pbx_ip'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;jsonb_typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'sender_information'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'object'&lt;/span&gt;
                     &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'sender_information'&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'ntp_timestamp_sec'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s1"&gt;'^[0-9]+$'&lt;/span&gt;
                     &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'sender_information'&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'ntp_timestamp_sec'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;far_end_sr&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;dialogs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x_oid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t200&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;             &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'unknown'&lt;/span&gt;
       &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pbx_with_block&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;                    &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'refuted'&lt;/span&gt;
       &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unclear&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pbx_post&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;         &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'unknown'&lt;/span&gt;
       &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pbx_post_none&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pbx_post&lt;/span&gt;            &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'confirmed'&lt;/span&gt;
       &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'unknown'&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;verdict&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The INVITE window allows 3 s of clock skew after the open. The verdict is &lt;code&gt;unknown&lt;/code&gt; unless exactly one dialog matched and it has a PBX &lt;code&gt;200&lt;/code&gt; whose &lt;code&gt;CSeq&lt;/code&gt; method is &lt;code&gt;INVITE&lt;/code&gt;, or if any report is &lt;code&gt;unclear&lt;/code&gt; (unparsed, missing, malformed or empty &lt;code&gt;report_blocks&lt;/code&gt;). All 26 incident calls came back &lt;code&gt;confirmed&lt;/code&gt;, each matching exactly 1 dialog with no carrier re-INVITEs and no &lt;code&gt;sendonly&lt;/code&gt;/&lt;code&gt;inactive&lt;/code&gt;. A healthy control call came back &lt;code&gt;refuted&lt;/code&gt;. The 3.7 s early-media call came back &lt;code&gt;unknown&lt;/code&gt;. Always bound &lt;code&gt;create_date&lt;/code&gt;: a day-wide version of a similar join hit my 5-minute timeout.&lt;/p&gt;

&lt;h2&gt;
  
  
  How did one SIP header point to a shared upstream?
&lt;/h2&gt;

&lt;p&gt;The 26 calls came through three doors: 14 on Carrier A's direct trunk, 7 via Carrier B's NL-labelled SBC and 5 via its DE-labelled SBC. Three trunks failing in the same 44 minutes suggested something shared upstream.&lt;/p&gt;

&lt;p&gt;The clue was in Carrier B's &lt;code&gt;INVITE&lt;/code&gt;s, which carry an extra header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;INVITE sip:...                              (from Carrier B's SBC)
Call-ID: &amp;lt;a UUID, Carrier B's own format&amp;gt;
X-OID: &amp;lt;an ID in Carrier A's Call-ID format&amp;gt;

INVITE sip:...                              (from Carrier A's direct trunk)
Call-ID: &amp;lt;Carrier A's format&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Carrier B's own Call-IDs are UUIDs. The &lt;code&gt;X-OID&lt;/code&gt; value has the same distinctive multi-field layout as Carrier A's Call-IDs on my direct trunk. It was on all 12 Carrier B dead calls, and that day on 651 of 651 inbound dialogs from the DE-labelled SBC and 346 of 349 from the NL-labelled one. The three without it showed one of our own extension or agent names in the &lt;code&gt;From&lt;/code&gt; display name; I didn't dig further.&lt;/p&gt;

&lt;p&gt;That doesn't &lt;em&gt;prove&lt;/em&gt; Carrier A originated them. Shared SBC software could produce the same format, and simultaneous failures don't locate a fault. But it changed what I did: I widened the watcher, which had missed the 12 Carrier B calls, and put &lt;code&gt;X-OID&lt;/code&gt; in every alert and ticket. Carrier B can confirm it against the upstream leg in its own signalling trace. Look at a full &lt;code&gt;INVITE&lt;/code&gt; from each of your trunks for similar headers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I keep the watcher from going silently blind?
&lt;/h2&gt;

&lt;p&gt;A detector that finds nothing looks healthy. Each guard is a reason to investigate, not a diagnosis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Empty is never clean.&lt;/strong&gt; No strict-RTP lines? Check the PBX stream has &lt;em&gt;any&lt;/em&gt; recent lines; warn after 24 hours without them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Freshness.&lt;/strong&gt; Newest PBX line older than 45 s? Skip the pass without advancing coverage. Tune it to your traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coverage per trunk.&lt;/strong&gt; Hourly, for each trunk with 10+ inbound calls, count how many opened a stream on a mapped address. Below half means the media moved, the log format changed, or the classifier broke. One carrier's media move left my watcher blind until this check existed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep Homer off the critical path.&lt;/strong&gt; Short timeout, per-pass budget. A candidate still unsettled after 10 minutes is posted as "unconfirmed" only if bridged within 20 s of the open, in the bridge 5 s or more, and still unclosed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replay.&lt;/strong&gt; A &lt;code&gt;--dry-run&lt;/code&gt; mode with &lt;code&gt;--start/--end&lt;/code&gt; runs the same rule over a past window and writes nothing. I replayed this version over the previous week before switching.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What doesn't this catch?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Silent RTP.&lt;/strong&gt; Packets can flow with 0% loss and carry near-silence. For a complaint, check the recording's inbound leg.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Media that stops later.&lt;/strong&gt; A stream that locks and then dies mid-call is out of scope. So is a re-INVITE whose new stream never locks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Calls that never reach a bridge.&lt;/strong&gt; See the queued call above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who hung up.&lt;/strong&gt; Don't assume it was the caller giving up. In the 26 incident calls, the first &lt;code&gt;BYE&lt;/code&gt; came from the PBX side on 15 and from the carrier on 11.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Could my own firewall or NAT be the cause?&lt;/strong&gt; Possibly; this method alone can't rule it out. What pointed away from my side: 99 calls on the same trunks were fine in the same minutes, the trunks were clean the day before, and Carrier B's RTCP showed no Sender Reports on its dead calls. For certainty, capture packets on the PBX interface during an occurrence and compare with the carrier's capture.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sip</category>
      <category>voip</category>
    </item>
    <item>
      <title>Ghost Answers: Detecting SIP 200 OK Without SDP From Your Carrier</title>
      <dc:creator>Moisi Trungu</dc:creator>
      <pubDate>Tue, 13 Oct 2026 03:30:02 +0000</pubDate>
      <link>https://dev.to/moisi_trungu_31647b7ac300/ghost-answers-detecting-sip-200-ok-without-sdp-from-your-carrier-1kij</link>
      <guid>https://dev.to/moisi_trungu_31647b7ac300/ghost-answers-detecting-sip-200-ok-without-sdp-from-your-carrier-1kij</guid>
      <description>&lt;h1&gt;
  
  
  Ghost Answers: Detecting SIP 200 OK Without SDP From Your Carrier
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;A ghost answer is a call your carrier marks as answered with a &lt;code&gt;200 OK&lt;/code&gt; that carries no SDP, after which your own switch tears the call down the moment it is answered. Asterisk reports a healthy answer rate while your dialer logs a pile of zero-second no-answers. Here is how I tracked one carrier doing this, with the Homer SQL, Asterisk log lines and CDR joins I used to prove it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tested on: ViciDial 2.14 (SVN r3988) with Asterisk 18.26 (chan_sip), MariaDB 10.11, and Homer 7 using heplify-server 1.59.6 with the &lt;code&gt;homer7&lt;/code&gt; schema on PostgreSQL 16. In this install &lt;code&gt;hep_proto_1_call.raw&lt;/code&gt; is a &lt;code&gt;varchar&lt;/code&gt;. If yours stores &lt;code&gt;raw&lt;/code&gt; as &lt;code&gt;bytea&lt;/code&gt;, wrap it as &lt;code&gt;convert_from(raw, 'UTF8')&lt;/code&gt; before applying the text functions below.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a ghost answer?
&lt;/h2&gt;

&lt;p&gt;On a normal outbound SIP call the far end answers with &lt;code&gt;200 OK&lt;/code&gt; and an SDP body that says where to send audio and which codec to use. Your side sends &lt;code&gt;ACK&lt;/code&gt;, RTP flows, and the call has a conversation.&lt;/p&gt;

&lt;p&gt;A ghost answer breaks that. The carrier sends &lt;code&gt;200 OK&lt;/code&gt; with an empty body. No SDP, no media address, no codec. Depending on what happened earlier in the call, Asterisk either falls back to the media details from an earlier &lt;code&gt;183 Session Progress&lt;/code&gt;, or it gives up and sends &lt;code&gt;BYE&lt;/code&gt; right after its own &lt;code&gt;ACK&lt;/code&gt;. In the second case the gap between &lt;code&gt;ACK&lt;/code&gt; and &lt;code&gt;BYE&lt;/code&gt; is typically a fraction of a millisecond.&lt;/p&gt;

&lt;p&gt;Is an SDP-less 200 OK legal? It depends. With reliable provisional responses (&lt;code&gt;100rel&lt;/code&gt; and PRACK, RFC 3262) the answer can arrive in a &lt;code&gt;183&lt;/code&gt;, and the 200 does not have to repeat it. Without them, RFC 3261 section 13.2.1 is blunt: when the offer is in the INVITE, the answer must be in a reliable message, "For this specification, that is only the final 2xx response to that INVITE."&lt;/p&gt;

&lt;p&gt;In my capture window, all 23,167 initial INVITEs to my three carriers carried an SDP offer, none advertised &lt;code&gt;100rel&lt;/code&gt;, no 180 or 183 carried &lt;code&gt;RSeq&lt;/code&gt; or &lt;code&gt;Require: 100rel&lt;/code&gt;, and Homer saw zero PRACKs. So these 200s were missing the SDP answer that this exchange required. Beyond the RFC, what makes it a ghost answer is scale and outcome: one route doing it on most answers while your other routes do it on none, and a large share of those calls being torn down instantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  How did the problem first show up in my numbers?
&lt;/h2&gt;

&lt;p&gt;I run a press-1 survey campaign that splits outbound traffic randomly across three carriers, one third each, on the same lead lists. One morning the dialer-side answer rate on one carrier, Carrier A, was half of what it had been the day before. The other two had not moved.&lt;/p&gt;

&lt;p&gt;The odd part was that the Asterisk-side answer rate had barely changed. ViciDial's &lt;code&gt;vicidial_carrier_log&lt;/code&gt; table records the &lt;code&gt;DIALSTATUS&lt;/code&gt; Asterisk saw for each call. Joining that to &lt;code&gt;vicidial_log&lt;/code&gt; (the disposition the dialer finally recorded for the lead) showed the gap:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Carrier&lt;/th&gt;
&lt;th&gt;Dials&lt;/th&gt;
&lt;th&gt;Asterisk ANSWER rate&lt;/th&gt;
&lt;th&gt;Dialer answer rate&lt;/th&gt;
&lt;th&gt;ANSWER but logged NA&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Day 1&lt;/td&gt;
&lt;td&gt;Carrier A&lt;/td&gt;
&lt;td&gt;88,019&lt;/td&gt;
&lt;td&gt;32.2%&lt;/td&gt;
&lt;td&gt;31.2%&lt;/td&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 1&lt;/td&gt;
&lt;td&gt;Carrier B&lt;/td&gt;
&lt;td&gt;87,962&lt;/td&gt;
&lt;td&gt;38.3%&lt;/td&gt;
&lt;td&gt;36.2%&lt;/td&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 1&lt;/td&gt;
&lt;td&gt;Carrier C&lt;/td&gt;
&lt;td&gt;87,483&lt;/td&gt;
&lt;td&gt;40.0%&lt;/td&gt;
&lt;td&gt;39.1%&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 2&lt;/td&gt;
&lt;td&gt;Carrier A&lt;/td&gt;
&lt;td&gt;84,793&lt;/td&gt;
&lt;td&gt;31.6%&lt;/td&gt;
&lt;td&gt;15.6%&lt;/td&gt;
&lt;td&gt;13,041&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 2&lt;/td&gt;
&lt;td&gt;Carrier B&lt;/td&gt;
&lt;td&gt;85,337&lt;/td&gt;
&lt;td&gt;38.3%&lt;/td&gt;
&lt;td&gt;36.1%&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Day 2&lt;/td&gt;
&lt;td&gt;Carrier C&lt;/td&gt;
&lt;td&gt;85,363&lt;/td&gt;
&lt;td&gt;39.1%&lt;/td&gt;
&lt;td&gt;38.2%&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Asterisk ANSWER rate" is the share of dials where &lt;code&gt;Dial()&lt;/code&gt; returned &lt;code&gt;ANSWER&lt;/code&gt;. It is measured on my side, not taken from the carrier's reports. "Dialer answer rate" is the share of dials whose ViciDial disposition was anything other than a no-answer, busy, disconnect or drop.&lt;/p&gt;

&lt;p&gt;Carrier A's ANSWER rate stayed around 32%. But overnight, roughly half of those answers turned into &lt;code&gt;NA&lt;/code&gt; records in &lt;code&gt;vicidial_log&lt;/code&gt;, every one of them with &lt;code&gt;length_in_sec = 0&lt;/code&gt;. Over the next seven dialing days Carrier A produced between 12,963 and 17,459 of these per day. The other two carriers stayed between 10 and 60 per day.&lt;/p&gt;

&lt;p&gt;The signal is the disagreement between the two rates; either one alone looks unremarkable.&lt;/p&gt;

&lt;p&gt;That table is from an incident a few months ago. My Homer instance only keeps three days of SIP, so I no longer have the signaling for those days. The same carrier was still doing it when I wrote this, and every SIP-level number below comes from a live capture of that current traffic. That the July discrepancy had the same cause is an inference, not something I can still prove from packets.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I measure the gap between Asterisk answers and dialer answers?
&lt;/h2&gt;

&lt;p&gt;You need two things: the per-call &lt;code&gt;DIALSTATUS&lt;/code&gt; from Asterisk, and a way to know which carrier each call took. ViciDial gives you the first in &lt;code&gt;vicidial_carrier_log&lt;/code&gt;. For the second, I log the carrier at dial time into a small attribution table (&lt;code&gt;carrier_route_log&lt;/code&gt; here: &lt;code&gt;uniqueid&lt;/code&gt;, &lt;code&gt;carrier&lt;/code&gt;, &lt;code&gt;call_date&lt;/code&gt;) from the dialplan. That table joins to &lt;code&gt;vicidial_carrier_log&lt;/code&gt; on &lt;code&gt;uniqueid&lt;/code&gt;, because both are written with the dial-time uniqueid.&lt;/p&gt;

&lt;p&gt;The join to &lt;code&gt;vicidial_log&lt;/code&gt; is the tricky part. On my install, answered calls in &lt;code&gt;vicidial_log&lt;/code&gt; do not carry the dial-time uniqueid: on one day, 9,542 answered-status rows had just 1 match in my attribution table, while all 16,744 no-answer/busy/drop rows matched. A &lt;code&gt;uniqueid&lt;/code&gt; join therefore loses exactly the calls you care about. I join on &lt;code&gt;lead_id&lt;/code&gt; plus a time window instead, and count matches per call so I can see when the join misbehaves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;dials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'ANSWER'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ast_answer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'ANSWER'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ast_answer_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nmatch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;unmatched&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nmatch&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;multi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'ANSWER'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'NA'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ans_but_na&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'ANSWER'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'NA'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ans_but_na_0s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nmatch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'AB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'ADC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'DC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'B'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'CONGST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'DROP'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;dialer_answer_pct&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniqueid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dialstatus&lt;/span&gt; &lt;span class="n"&gt;ds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniqueid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;nmatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length_in_sec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_carrier_log&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;carrier_route_log&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniqueid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;-- scope the dial cohort to the campaign BEFORE joining dispositions&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;   &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;
  &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_lists&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;list_id&lt;/span&gt;
                        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;ls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'YOUR_CAMPAIGN'&lt;/span&gt;
  &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'YOUR_CAMPAIGN'&lt;/span&gt;
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;SECOND&lt;/span&gt;
                        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt; &lt;span class="k"&gt;SECOND&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;CURDATE&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;  &lt;span class="n"&gt;CURDATE&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniqueid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The campaign filter has to sit on the dial cohort, not just on the &lt;code&gt;vicidial_log&lt;/code&gt; side. Otherwise other campaigns' dials stay in the denominator and simply show up as unmatched. I scope by the campaign that owns the lead's list. On my install that removed 98 dials from another campaign on one day out of more than two million, and no other day had any. Before trusting it, check three things for your interval:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One dial per lead per day.&lt;/strong&gt; On every day in my table, zero leads were dialed more than once, so a &lt;code&gt;lead_id&lt;/code&gt; join cannot pick up the wrong attempt. Check yours with a &lt;code&gt;GROUP BY lead_id, DATE(call_date) HAVING COUNT(*) &amp;gt; 1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;multi&lt;/code&gt; is near zero.&lt;/strong&gt; Mine never exceeded 1 per carrier per day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;unmatched&lt;/code&gt; is explained.&lt;/strong&gt; Mine were mostly &lt;code&gt;BUSY&lt;/code&gt; calls whose &lt;code&gt;vicidial_log&lt;/code&gt; row landed outside the window. Between 1.8% and 5.6% of &lt;code&gt;ANSWER&lt;/code&gt; rows were unmatched on the day I checked, so the dialer answer rate is a slight undercount for all three carriers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The window is wide on purpose. In the sample call I trace below, the &lt;code&gt;vicidial_log&lt;/code&gt; row was stamped 43 seconds after the dial started.&lt;/p&gt;

&lt;p&gt;Treat &lt;code&gt;ans_but_na&lt;/code&gt; as a screening metric, not a ghost counter. An &lt;code&gt;ANSWER&lt;/code&gt; that ends up &lt;code&gt;NA&lt;/code&gt; with zero length is what a ghost answer looks like in the CDRs, but other faults could produce the same row. On a clean route it is a few dozen per day out of tens of thousands of answers. When one route jumps to thousands, go to the SIP capture to confirm what is happening.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does a ghost answer look like in Asterisk logs?
&lt;/h2&gt;

&lt;p&gt;chan_sip logs a warning when it receives an SDP-less &lt;code&gt;200 OK&lt;/code&gt; it cannot use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WARNING[10434][C-01204f61] chan_sip.c: Received response: "200 OK" from 'carrier_a' without SDP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is one complete ghost call from the &lt;code&gt;full&lt;/code&gt; log, trimmed to the interesting lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[09:02:16] app_dial.c: Called SIP/carrier_a/39XXXXXXXXXX
[09:02:19] app_dial.c: SIP/carrier_a-00d92d01 is making progress passing it to Local/...;2
[09:02:19] app_dial.c: Call on SIP/carrier_a-00d92d01 placed on hold
[09:02:19] res_musiconhold.c: Started music on hold, class 'default', on channel 'Local/...;2'
[09:02:28] chan_sip.c: Received response: "200 OK" from 'carrier_a' without SDP
[09:02:28] app_dial.c: SIP/carrier_a-00d92d01 answered Local/...;2
[09:02:28] bridge_channel.c: Channel SIP/carrier_a-00d92d01 joined 'simple_bridge' basic-bridge &amp;lt;...&amp;gt;
[09:02:28] bridge_channel.c: Channel SIP/carrier_a-00d92d01 left 'simple_bridge' basic-bridge &amp;lt;...&amp;gt;
[09:02:28] pbx.c: Executing [h@default:1] AGI("Local/...;2", "agi://127.0.0.1:4577/call_log--HVcauses--PRI-----NODEBUG-----16-----ANSWER-----11-----0-----SIP 200 OK)")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The SIP channel joins the bridge and leaves it in the same second. The hangup handler records &lt;code&gt;DIALSTATUS=ANSWER&lt;/code&gt; after 11 seconds of dial time. ViciDial then logged this lead as &lt;code&gt;NA&lt;/code&gt; with &lt;code&gt;length_in_sec = 0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These commands assume the default &lt;code&gt;full&lt;/code&gt; log format with timestamps like &lt;code&gt;[Oct  5 09:02:29]&lt;/code&gt;. To count warnings per hour per peer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="nt"&gt;-F&lt;/span&gt;&lt;span class="s2"&gt;"'"&lt;/span&gt; &lt;span class="s1"&gt;'/Received response: "200 OK" from .* without SDP/ {
  split($0, a, ":"); print substr(a[1], 2), $2 }'&lt;/span&gt; /var/log/asterisk/full | &lt;span class="nb"&gt;sort&lt;/span&gt; | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to compare against total answers on that carrier over the same hour:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/^\[Oct  5 09:/'&lt;/span&gt; /var/log/asterisk/full | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-cE&lt;/span&gt; &lt;span class="s1"&gt;'app_dial.c(:[0-9]+)?: SIP/carrier_a-[0-9a-f]+ answered'&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/^\[Oct  5 09:/'&lt;/span&gt; /var/log/asterisk/full | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-cE&lt;/span&gt; &lt;span class="s1"&gt;'Received response: "200 OK" from .carrier_a. without SDP'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use ISO timestamps (&lt;code&gt;dateformat&lt;/code&gt; in &lt;code&gt;logger.conf&lt;/code&gt;), adjust the hour extraction to match.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the Asterisk warning catch every SDP-less 200 OK?
&lt;/h2&gt;

&lt;p&gt;No, and this matters. On one dialer, Homer saw 1,304 SDP-less &lt;code&gt;200 OK&lt;/code&gt; answers from Carrier A during my window, but Asterisk logged far fewer warnings. To see which calls the warnings belonged to, I matched them one by one. For every call answered before the cutoff where Asterisk sent &lt;code&gt;BYE&lt;/code&gt; within 50 ms of its &lt;code&gt;ACK&lt;/code&gt;, I took the dialed number from Homer. Then I matched each warning line to the dialed number in the &lt;code&gt;Called SIP/...&lt;/code&gt; line with the same &lt;code&gt;C-&lt;/code&gt; call ID. On that dialer, 595 calls appeared in both lists, with none left over on either side. On a second dialer it was 84 and 84.&lt;/p&gt;

&lt;p&gt;So each warning is one call that Asterisk killed, and SDP-less answers that survived produce no warning. The reason is in chan_sip's &lt;code&gt;handle_response_invite()&lt;/code&gt; (Asterisk 18 branch source; the build I tested was 18.26.4):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="err"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;reinvite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ast_sockaddr&lt;/span&gt; &lt;span class="n"&gt;remote_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,}};&lt;/span&gt;

    &lt;span class="n"&gt;ast_rtp_instance_get_requested_target_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;remote_address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ast_sockaddr_isnull&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;remote_address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ast_strlen_zero&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;theirprovtag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;strcmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;theirtag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;theirprovtag&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ast_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LOG_WARNING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Received response: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;200 OK&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt; from '%s' without SDP&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;relatedpeer&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;ast_set_flag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;SIP_PENDINGBYE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;ast_rtp_instance_activate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rtp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a &lt;code&gt;200 OK&lt;/code&gt; arrives without SDP, chan_sip checks whether it already has a remote media address (normally from an earlier &lt;code&gt;183&lt;/code&gt; with SDP) and whether the To-tag matches the provisional response. If the address is missing, or the tag changed, it logs the warning and sets &lt;code&gt;SIP_PENDINGBYE&lt;/code&gt;, which schedules a BYE once the ACK is out. If it already has a usable address, it silently carries on with the early-media details, and the call can survive.&lt;/p&gt;

&lt;p&gt;The practical lesson: a &lt;code&gt;grep 'without SDP'&lt;/code&gt; count is a count of warning events, which on my system matched the killed calls one for one. Use Homer for the real count of SDP-less answers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I find ghost answers in Homer?
&lt;/h2&gt;

&lt;p&gt;heplify-server writes SIP calls into &lt;code&gt;hep_proto_1_call&lt;/code&gt; (registrations and other methods go to separate tables), with the full message in &lt;code&gt;raw&lt;/code&gt;, addresses in &lt;code&gt;protocol_header&lt;/code&gt;, and parsed &lt;code&gt;cseq&lt;/code&gt;, &lt;code&gt;method&lt;/code&gt; and &lt;code&gt;callid&lt;/code&gt; in &lt;code&gt;data_header&lt;/code&gt;. Using the parsed fields is more robust than regexing headers yourself, because heplify has already handled case and compact header forms.&lt;/p&gt;

&lt;p&gt;The queries below count answered dialogs, not messages, and must run in one &lt;code&gt;psql&lt;/code&gt; session because they build temp tables and use &lt;code&gt;psql&lt;/code&gt; variables. They take each initial INVITE transaction (no To-tag) that your dialers sent inside a cohort window, keep the first &lt;code&gt;200 OK&lt;/code&gt; per dialog, and check the body for real SDP instead of trusting &lt;code&gt;Content-Length&lt;/code&gt;. A dialog is matched exactly on Call-ID, our From-tag and the carrier's To-tag, the tags compared as parsed values, so a forked answer would show up as its own row. Messages are collected over a longer observation window than the INVITE cohort, so a call dialed at 09:59:58 and answered at 10:00:02 is still counted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="s1"&gt;'UTC'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- cohort = INVITEs sent in [c_start, c_end); observe their messages until o_end&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;c_start&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;2026-10-05 00:00&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;c_end&lt;/span&gt;   &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;2026-10-05 07:45&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;o_end&lt;/span&gt;   &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;2026-10-05 08:00&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;-- 1. every message of every call your dialers sent to a carrier in the cohort window&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;create_date&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;protocol_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'srcIp'&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protocol_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'dstIp'&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;data_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'method'&lt;/span&gt;   &lt;span class="n"&gt;meth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;data_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'callid'&lt;/span&gt;   &lt;span class="n"&gt;callid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;data_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'from_tag'&lt;/span&gt; &lt;span class="n"&gt;ftag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;split_part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'cseq'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;cseqn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;split_part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'cseq'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;cseqm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;split_part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
         &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'(?in)^(?:to|t)[ &lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s1"&gt;]*:[^&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s1"&gt;]*;[ &lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s1"&gt;]*tag=([^;&amp;gt;,[:space:]]+)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ttag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;split_part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\r\n\r\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;hep_proto_1_call&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;create_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;c_start&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;create_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;o_end&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;sid&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;sid&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;hep_proto_1_call&lt;/span&gt;
              &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;create_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;c_start&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;create_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;c_end&lt;/span&gt;
                &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;data_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'method'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'INVITE'&lt;/span&gt;
                &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;protocol_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'dstIp'&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'203.0.113.10'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'203.0.113.20'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'203.0.113.30'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;-- carrier IPs&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- 2. initial INVITE transactions (no To-tag) sent inside the cohort window&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;callid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cseqn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ftag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ftag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;dialer&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;meth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'INVITE'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;ttag&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'203.0.113.10'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'203.0.113.20'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'203.0.113.30'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;callid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cseqn&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;c_end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 3. first 200 OK per answered dialog: Call-ID + our From-tag + their To-tag&lt;/span&gt;
&lt;span class="c1"&gt;--    (one row per fork if the carrier answers more than one early dialog)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cseqn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
       &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cseqn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ftag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dialer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;t200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s1"&gt;'(?n)^v=0'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s1"&gt;'(?n)^m=audio'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;has_sdp&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callid&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cseqn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cseqn&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cseqm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'INVITE'&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'200'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;carrier&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ftag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ftag&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cseqn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 4. per-carrier share of answered calls with no SDP, plus sanity counters&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;answered_dialogs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;has_sdp&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;no_sdp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;has_sdp&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;pct_no_sdp&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;carrier&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;cohort_invites&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;callid&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;callid&lt;/span&gt;
                              &lt;span class="k"&gt;HAVING&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;calls_with_forks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callid&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cseqn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cseqn&lt;/span&gt;
             &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cseqm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'INVITE'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meth&lt;/span&gt; &lt;span class="o"&gt;~&lt;/span&gt; &lt;span class="s1"&gt;'^[2-6][0-9][0-9]$'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The window values are an example; they are what I used. The cohort is calls whose INVITE went out between midnight and 07:45 UTC on the day I checked (dialing started around 07:00), observed until 08:00. It returned:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Carrier&lt;/th&gt;
&lt;th&gt;Answered calls&lt;/th&gt;
&lt;th&gt;200 OK without SDP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Carrier A&lt;/td&gt;
&lt;td&gt;2,366&lt;/td&gt;
&lt;td&gt;1,475 (62.3%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carrier B&lt;/td&gt;
&lt;td&gt;2,755&lt;/td&gt;
&lt;td&gt;0 (0.0%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Carrier C&lt;/td&gt;
&lt;td&gt;3,682&lt;/td&gt;
&lt;td&gt;0 (0.0%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All 1,475 of Carrier A's SDP-less 200s had a completely empty body. heplify-server's &lt;code&gt;Dedup&lt;/code&gt; option was off on my install. I checked the window for duplicate captures and retransmitted INVITEs or 200s and found none. Of the 23,167 cohort INVITEs, none had more than one answered fork and none were still pending at the end of the observation window.&lt;/p&gt;

&lt;p&gt;Run it as &lt;code&gt;docker exec -i &amp;lt;postgres-container&amp;gt; psql -U homer -d homer_data&lt;/code&gt; if Homer runs in Docker. The &lt;code&gt;SET TIME ZONE 'UTC'&lt;/code&gt; makes the window and any timestamps you print explicit. On my system Asterisk logs in local time (UTC+2), so the same call shows up two hours apart in the two sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does my Homer query flag clean carriers too?
&lt;/h2&gt;

&lt;p&gt;Because of one innocent-looking filter. The obvious quick way to restrict raw messages to answers of INVITEs is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%CSeq:%INVITE%'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That also matches the &lt;code&gt;200 OK&lt;/code&gt; to a &lt;code&gt;BYE&lt;/code&gt; or &lt;code&gt;CANCEL&lt;/code&gt;, because those responses carry an &lt;code&gt;Allow:&lt;/code&gt; header after the CSeq line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;CSeq: 104 BYE
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH, MESSAGE
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;%CSeq:%INVITE%&lt;/code&gt; happily spans from &lt;code&gt;CSeq:&lt;/code&gt; to the &lt;code&gt;INVITE&lt;/code&gt; in &lt;code&gt;Allow:&lt;/code&gt;. Counting raw &lt;code&gt;200 OK&lt;/code&gt; messages from my three carriers over the morning, the sloppy filter returned 12,453 messages. Only 8,754 of them were answers to an INVITE.&lt;/p&gt;

&lt;p&gt;Those BYE and CANCEL responses normally have no body, so they look exactly like ghost answers. With the sloppy filter, Carrier C had 7,363 matching messages instead of 3,664, and showed up as 50.2% "without SDP" when the real figure was zero. Carriers A and B were unaffected only because none of their BYE or CANCEL responses matched the pattern. You cannot count on that.&lt;/p&gt;

&lt;p&gt;If you do filter raw text, anchor the method to the CSeq line itself (&lt;code&gt;raw ~ 'CSeq: [0-9]+ INVITE'&lt;/code&gt;). Better still, use the parsed &lt;code&gt;data_header-&amp;gt;&amp;gt;'cseq'&lt;/code&gt; field as in the query above.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I measure how fast the call dies?
&lt;/h2&gt;

&lt;p&gt;For each SDP-less answer, find the &lt;code&gt;ACK&lt;/code&gt; for that INVITE CSeq in the same dialog (same Call-ID, From-tag and To-tag), and the first &lt;code&gt;BYE&lt;/code&gt; in that dialog in either direction. A BYE we send carries our tag in From; a BYE from the carrier has the tags swapped. Then classify, keeping incomplete and impossible cases visible instead of letting them fall into a bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- same psql session as the previous query (reuses temp tables m and ok)&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;MIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callid&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ACK'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cseqn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cseqn&lt;/span&gt;
      &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ftag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ftag&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttag&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;tack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="n"&gt;tbye&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="n"&gt;bye_src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dir&lt;/span&gt; &lt;span class="n"&gt;bye_dir&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;LATERAL&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
         &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ftag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ftag&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'ours'&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'theirs'&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;dir&lt;/span&gt;
  &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;
  &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callid&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;meth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'BYE'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;t200&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ftag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ftag&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttag&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;-- BYE sent by us&lt;/span&gt;
      &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ftag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttag&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ttag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ftag&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;     &lt;span class="c1"&gt;-- BYE sent by the carrier&lt;/span&gt;
  &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;has_sdp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;tack&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="n"&gt;tbye&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'incomplete'&lt;/span&gt;
            &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;tbye&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tack&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'negative'&lt;/span&gt;
            &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;tbye&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tack&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'50 ms'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'instant'&lt;/span&gt;
            &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'survived'&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;bye_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ours'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;bye_from_us&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;percentile_cont&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;WITHIN&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt;
         &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;tbye&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tack&lt;/span&gt;&lt;span class="p"&gt;)))::&lt;/span&gt;&lt;span class="nb"&gt;numeric&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;median_ms&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt; &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of Carrier A's 1,475 SDP-less answers, 681 (46.2%) were instant: the BYE followed the ACK by a median of 0.038 ms, and by at most 18 ms. In all 681 the BYE was ours: our tag in From, sent from the same dialer IP the 200 OK was delivered to. From the 200 OK itself to the BYE, the median was 0.3 ms. The other 794 survived, with a median of 5.4 seconds between &lt;code&gt;ACK&lt;/code&gt; and &lt;code&gt;BYE&lt;/code&gt;. None were negative, and none were incomplete. If you run it while calls are still up, the last few minutes of the cohort will show &lt;code&gt;incomplete&lt;/code&gt; rows, which is what that bucket is for.&lt;/p&gt;

&lt;p&gt;These calls had 183s with SDP, so early media could have flowed before the answer, and I have not checked RTP for them. What a BYE less than a millisecond after the ACK does show is that my side tore the call down immediately on answer, leaving no room for a post-answer conversation. If you need to prove what audio was or wasn't present, you need the RTP or a recording. Signaling alone won't tell you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the SIP ladder look like?
&lt;/h2&gt;

&lt;p&gt;Here is the Homer ladder for the same call I showed from the Asterisk log (addresses replaced with documentation ranges, timestamps UTC):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;07:02:16.827944  dialer  -&amp;gt; carrier  INVITE                       CSeq: 102 INVITE  CL: 334
07:02:16.830601  carrier -&amp;gt; dialer   100 Giving it a try          CSeq: 102 INVITE  CL: 0
07:02:19.240142  carrier -&amp;gt; dialer   183 Session Progress         CSeq: 102 INVITE  CL: 254  c=198.51.100.7
07:02:19.433423  carrier -&amp;gt; dialer   183 Session Progress         CSeq: 102 INVITE  CL: 254  c=203.0.113.10
07:02:19.546739  carrier -&amp;gt; dialer   183 Session Progress         CSeq: 102 INVITE  CL: 254  c=203.0.113.10
07:02:19.661336  carrier -&amp;gt; dialer   183 Session Progress         CSeq: 102 INVITE  CL: 254  c=203.0.113.10
07:02:28.020534  carrier -&amp;gt; dialer   200 OK                       CSeq: 102 INVITE  CL: 0
07:02:28.042133  dialer  -&amp;gt; carrier  ACK                          CSeq: 102 ACK
07:02:28.042431  dialer  -&amp;gt; carrier  BYE                          CSeq: 103 BYE
07:02:28.045899  carrier -&amp;gt; dialer   200 OK                       CSeq: 103 BYE
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;ACK&lt;/code&gt; and the &lt;code&gt;BYE&lt;/code&gt; are 0.3 ms apart, and the BYE went out 22 ms after the 200 OK arrived. If you prefer watching live on the dialer, &lt;code&gt;sngrep -c host 203.0.113.10&lt;/code&gt; shows only INVITE dialogs to and from that carrier, and you can open any call to get the same ladder.&lt;/p&gt;

&lt;p&gt;To pull a ladder like this from Homer, grab the &lt;code&gt;sid&lt;/code&gt; of the INVITE and list every message in that dialog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="nb"&gt;TIME&lt;/span&gt; &lt;span class="k"&gt;ZONE&lt;/span&gt; &lt;span class="s1"&gt;'UTC'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;to_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'HH24:MI:SS.US'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;protocol_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'srcIp'&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;protocol_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'dstIp'&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;split_part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;E&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;first_line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;data_header&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'cseq'&lt;/span&gt; &lt;span class="n"&gt;cseq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'(?i)Content-Length: *[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;'c=IN IP4 [0-9.]+'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;c_line&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;hep_proto_1_call&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;sid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'&amp;lt;sid&amp;gt;'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;create_date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why do some ghost answers die and others survive?
&lt;/h2&gt;

&lt;p&gt;Look at the SDP in those 183s. The first one says &lt;code&gt;a=sendrecv&lt;/code&gt; from one media address. The second switches to a different address and &lt;code&gt;a=sendonly&lt;/code&gt;. That matches the "placed on hold" log line from the same second.&lt;/p&gt;

&lt;p&gt;I checked this across the whole sample. For each SDP-less call I took the last &lt;code&gt;183&lt;/code&gt; with SDP that arrived before the 200 OK in the same dialog. I split its SDP into media sections at each &lt;code&gt;m=&lt;/code&gt; line and read the direction attribute from the audio section, falling back to a session-level attribute (before the first &lt;code&gt;m=&lt;/code&gt;) and then to the &lt;code&gt;sendrecv&lt;/code&gt; default. Every one of those 183 SDPs had exactly one media section, audio. For 680 of the 681 instant kills the direction was &lt;code&gt;sendonly&lt;/code&gt;, and the remaining one had no 183 with SDP at all. The two clean carriers sent about 7,000 183 messages with SDP each, and none of them had a &lt;code&gt;sendonly&lt;/code&gt; audio section. The killed calls also averaged about 1.7 distinct media addresses across their 183s, versus about 0.7 for the survivors.&lt;/p&gt;

&lt;p&gt;I'd call that a strong correlation rather than the full mechanism. Among the 794 survivors, 120 also ended ringing on a &lt;code&gt;sendonly&lt;/code&gt; 183 and still lived, and 384 had no 183 with SDP at all. So &lt;code&gt;sendonly&lt;/code&gt; alone does not decide it. What I can say for certain is that the calls that die are the ones where chan_sip hits the branch in the code above. For detection you do not need to know more than that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I just fail over to another carrier on ghost answers?
&lt;/h2&gt;

&lt;p&gt;Not with a normal &lt;code&gt;DIALSTATUS&lt;/code&gt; failover. My dialplan already reroutes on carrier errors. This is a fragment: &lt;code&gt;FBCNT&lt;/code&gt; is initialized earlier, &lt;code&gt;DSTR&lt;/code&gt; holds the dial string, and the &lt;code&gt;end&lt;/code&gt; and &lt;code&gt;fb&lt;/code&gt; labels are defined further down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;88&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Dial&lt;/span&gt;(${&lt;span class="n"&gt;DSTR&lt;/span&gt;})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;88&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;GotoIf&lt;/span&gt;($[${&lt;span class="n"&gt;FBCNT&lt;/span&gt;}&amp;gt;=&lt;span class="m"&gt;2&lt;/span&gt;]?&lt;span class="n"&gt;end&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;88&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;GotoIf&lt;/span&gt;($[&lt;span class="s2"&gt;"${DIALSTATUS}"&lt;/span&gt;=&lt;span class="s2"&gt;"CONGESTION"&lt;/span&gt; | &lt;span class="s2"&gt;"${DIALSTATUS}"&lt;/span&gt;=&lt;span class="s2"&gt;"CHANUNAVAIL"&lt;/span&gt;]?&lt;span class="n"&gt;fb&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A ghost answer returns &lt;code&gt;DIALSTATUS=ANSWER&lt;/code&gt;, exactly like a real answer, so it bypasses this fallback completely. What happens to the lead afterwards depends on your campaign. On mine the call is dispositioned &lt;code&gt;NA&lt;/code&gt;, so whether it gets dialed again comes down to your recycling rules, not your dialplan.&lt;/p&gt;

&lt;p&gt;The response that works is at the route level:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reweight the split.&lt;/strong&gt; With a &lt;code&gt;RAND()&lt;/code&gt;-based split, take the ghosting carrier out of the random pick or give it a small share. On ViciDial, make the change in the carrier's dialplan entry in the admin GUI so it survives config regeneration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep a small share flowing if you need ongoing evidence.&lt;/strong&gt; A few percent of traffic is enough to keep the Homer query and the &lt;code&gt;ans_but_na&lt;/code&gt; column telling you whether the carrier fixed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-check daily.&lt;/strong&gt; In my case it went from a small baseline to thousands of calls a day overnight. It can stop or change just as abruptly, and you want to know when.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What evidence should I send the carrier?
&lt;/h2&gt;

&lt;p&gt;"Your ASR dropped" goes nowhere; their answer rate looks fine. Send them their own signaling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Homer per-carrier table&lt;/strong&gt;: share of answered calls whose &lt;code&gt;200 OK&lt;/code&gt; had no SDP, theirs versus your other carriers on the same traffic, same time window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two or three full SIP ladders&lt;/strong&gt; with Call-ID, timestamps and the raw &lt;code&gt;200 OK&lt;/code&gt;, showing the empty body. Include one where the 183 flips to &lt;code&gt;sendonly&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The RFC reference.&lt;/strong&gt; If the INVITE carried an SDP offer and the 183s were not sent reliably, quote RFC 3261 section 13.2.1. The 200 OK is missing the SDP answer this exchange required. That turns "your ASR looks odd" into a specific protocol fault their engineers can check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The daily table&lt;/strong&gt; of Asterisk ANSWER versus dialer-answered calls, starting a few days before the change, so the step is obvious.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An invoice question.&lt;/strong&gt; Ask them to confirm whether calls answered without SDP and released within a second are billed. If they are, ask for a credit on those calls from the start date.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep the Call-IDs in the ticket. Those are what their NOC can actually look up.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I watch for this going forward?
&lt;/h2&gt;

&lt;p&gt;Put two checks on a schedule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The per-call Homer query&lt;/strong&gt;, run hourly with the previous complete hour as the INVITE cohort and an observation window that runs at least 15 minutes past it, so calls answered after the hour boundary still count. Report the &lt;code&gt;pending&lt;/code&gt; counter alongside it. Alert when a carrier has a meaningful number of answered calls (I'd want at least a hundred) and more than a few percent of them have no SDP. Exclude calls where a reliable 183 (with &lt;code&gt;RSeq&lt;/code&gt;) already carried the SDP answer, because those 200s are legitimately empty. On my traffic there were none, and clean routes sat at 0.0%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;ans_but_na&lt;/code&gt; daily query&lt;/strong&gt;, run on the previous complete day. Alert on the ratio of &lt;code&gt;ans_but_na&lt;/code&gt; to &lt;code&gt;ast_answer&lt;/code&gt; per carrier rather than the raw count, so volume changes and partial days don't trigger it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your Homer retention is short, run the Homer check while the data still exists. Mine keeps three days, so a weekend is enough to lose the SIP evidence for a Friday incident. Save the query output and a handful of ladders as soon as you notice something.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is a 200 OK without SDP always fraud?&lt;/strong&gt; No. With 100rel and PRACK (RFC 3262) the answer can come in a reliable provisional response, and the 200 does not need to repeat it. I can't tell you whether my carrier did it on purpose or broke something. It becomes your problem when one route does it on most answers, your other routes do it on none, and the calls produce no conversation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this affect billing?&lt;/strong&gt; That depends on whether your carrier bills calls with a sub-second or zero answered duration. Check your invoice detail for the affected period before you assume either way.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sip</category>
      <category>voip</category>
    </item>
    <item>
      <title>AI Voice Agent Pilot vs Production: Lessons From Real Outbound Calls</title>
      <dc:creator>Moisi Trungu</dc:creator>
      <pubDate>Mon, 12 Oct 2026 03:30:02 +0000</pubDate>
      <link>https://dev.to/moisi_trungu_31647b7ac300/ai-voice-agent-pilot-vs-production-lessons-from-real-outbound-calls-3gl9</link>
      <guid>https://dev.to/moisi_trungu_31647b7ac300/ai-voice-agent-pilot-vs-production-lessons-from-real-outbound-calls-3gl9</guid>
      <description>&lt;h1&gt;
  
  
  AI Voice Agent Pilot vs Production: Lessons From Real Outbound Calls
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;A self-hosted AI phone agent can pass every demo and still fail on real outbound traffic. This is a case study of an Italian-language dental agent on Asterisk, fed by a ViciDial press-1 campaign. It covers what broke once real callers reached it: call identity, latency under load, answering machine detection, an overflowing context window, safety guards, and retrieval. The measured numbers come from the system's own traces, database rows, CDRs or test output. Where something is an observation and not a controlled result, I say so.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Configuration snapshot, September 2026: Asterisk 20.6 on the agent host; ViciDial on Asterisk 18.26.4-vici (ViciDial's patched build) on the dialer; AudioSocket; Silero VAD 6.2; faster-whisper 1.2.1 (large-v3); Ollama 0.30.8 with llama3.1:8b (&lt;code&gt;num_ctx&lt;/code&gt; 4096); Qdrant 1.18; one 20 GB workstation GPU. Italian TTS was a hosted streaming service, with local Kokoro/Piper code as a fallback. Earlier experiments ran on earlier spec versions of the same stack.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What was the setup?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ViciDial press-1 campaign -&amp;gt; SIP -&amp;gt; Asterisk (agent host)
  -&amp;gt; AGI (stage a session) -&amp;gt; AudioSocket (8 kHz 16-bit PCM over TCP)
  -&amp;gt; Python gateway: Silero VAD -&amp;gt; faster-whisper -&amp;gt; LLM (Ollama) -&amp;gt; TTS
  -&amp;gt; audio back over the same socket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ViciDial dials the leads. The callee hears a short recorded message and presses 1, and ViciDial sends the call to the agent host. The agent speaks Italian and has one job: find out whether the caller has a dental need, then get them to send two photos of their teeth over WhatsApp so a human can prepare a free quote.&lt;/p&gt;

&lt;p&gt;Basic demos sounded good. Targeted tests and real-call reviews did not. The checklist at the end sums up what we learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why did the audit call it "a pilot, not production"?
&lt;/h2&gt;

&lt;p&gt;About two weeks after real press-1 calls started, we ran a read-only production-readiness audit. Its first line said: "operational pilot, not ready for an unattended production service." The GPU, models and health checks were fine. What failed was the integration around them.&lt;/p&gt;

&lt;p&gt;The worst finding was a P0 in the first step every call runs. The dialplan called a shell AGI script before AudioSocket, and the script read its AGI environment like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; line&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;break
  export&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Asterisk sends AGI variables as &lt;code&gt;agi_request: something&lt;/code&gt;, with a colon and a space. That is not a shell assignment, and you can reproduce the failure in one line (&lt;code&gt;123.4&lt;/code&gt; is a made-up uniqueid):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/bin/sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'while read -r line; do [ -z "$line" ] &amp;amp;&amp;amp; break; export "$line"; done; echo REACHED'&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
agi_request: prepare.sh
agi_uniqueid: 123.4
&lt;/span&gt;&lt;span class="no"&gt;
EOF
&lt;/span&gt;&lt;span class="c"&gt;# /bin/sh: 1: export: agi_request: prepare.sh: bad variable name&lt;/span&gt;
&lt;span class="c"&gt;# exit=2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script died before it staged anything. In the gateway's &lt;code&gt;session_prepared&lt;/code&gt; events, agent, direction and provider reference were all null. The dialplan also passed a hard-coded UUID per language to &lt;code&gt;AudioSocket()&lt;/code&gt;, so every Italian call reached the gateway with the same &lt;code&gt;3333...&lt;/code&gt; UUID. The gateway used that UUID to pick the language line, not to identify the call. Linking a conversation back to its ViciDial lead took a timestamp join.&lt;/p&gt;

&lt;p&gt;A demo call doesn't show this, because the agent still answers and talks.&lt;/p&gt;

&lt;p&gt;The fix was a small Python AGI that treats AGI input as a protocol, never as shell code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_environment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Read AGI &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;agi_key: value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; lines until the blank line.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sep&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullmatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;agi_[a-z0-9_]+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lstrip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After reading the environment, the script sets an 8-second &lt;code&gt;signal.alarm&lt;/code&gt;. Inside that deadline it reads its config, checks the gateway readiness URL (when one is configured), logs in, generates &lt;code&gt;uuid.uuid4()&lt;/code&gt;, stages the session and returns the IDs with &lt;code&gt;SET VARIABLE&lt;/code&gt;. The deadline does not cover the initial environment read or the final &lt;code&gt;SET VARIABLE FLOW_STAGE_OK 0&lt;/code&gt; on the error path. If you copy this design, put those under a timeout too.&lt;/p&gt;

&lt;p&gt;The dialplan checks the result. This excerpt is from the deployed context (the channel is answered earlier, at &lt;code&gt;Answer()&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt; &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;FLOW_STAGE_OK&lt;/span&gt;=&lt;span class="m"&gt;0&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;AGI&lt;/span&gt;(&lt;span class="n"&gt;prepare&lt;/span&gt;.&lt;span class="n"&gt;sh&lt;/span&gt;,${&lt;span class="n"&gt;EXTEN&lt;/span&gt;})
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;GotoIf&lt;/span&gt;($[&lt;span class="s2"&gt;"${FLOW_STAGE_OK}"&lt;/span&gt; != &lt;span class="s2"&gt;"1"&lt;/span&gt;]?&lt;span class="n"&gt;flow_unavailable&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;ExecIf&lt;/span&gt;($[&lt;span class="s2"&gt;"${CALLSESSIONID}"&lt;/span&gt; != &lt;span class="s2"&gt;""&lt;/span&gt;]?&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CDR&lt;/span&gt;(&lt;span class="n"&gt;userfield&lt;/span&gt;)=${&lt;span class="n"&gt;CALLSESSIONID&lt;/span&gt;}))
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;ExecIf&lt;/span&gt;($[&lt;span class="s2"&gt;"${FLOW_VICI_CID}"&lt;/span&gt; != &lt;span class="s2"&gt;""&lt;/span&gt;]?&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CDR&lt;/span&gt;(&lt;span class="n"&gt;userfield&lt;/span&gt;)=${&lt;span class="n"&gt;CDR&lt;/span&gt;(&lt;span class="n"&gt;userfield&lt;/span&gt;)}|&lt;span class="n"&gt;vici&lt;/span&gt;:${&lt;span class="n"&gt;FLOW_VICI_CID&lt;/span&gt;}))
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;(&lt;span class="n"&gt;gw&lt;/span&gt;),&lt;span class="n"&gt;AudioSocket&lt;/span&gt;(${&lt;span class="n"&gt;FLOW_CALL_UUID&lt;/span&gt;},${&lt;span class="n"&gt;FLOW_AUDIO_GW&lt;/span&gt;})
 ...
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;(&lt;span class="n"&gt;flow_unavailable&lt;/span&gt;),&lt;span class="n"&gt;Playback&lt;/span&gt;(&lt;span class="n"&gt;custom&lt;/span&gt;/&lt;span class="n"&gt;agent&lt;/span&gt;-&lt;span class="n"&gt;unavailable&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Hangup&lt;/span&gt;(&lt;span class="m"&gt;41&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As deployed, the failure path plays an "unavailable" message and hangs up. That fails closed, but no human or callback destination exists yet.&lt;/p&gt;

&lt;p&gt;On the dialer side, ViciDial adds its caller code as a SIP header (&lt;code&gt;SIPAddHeader(X-vici-cid: ${CALLERID(name)})&lt;/code&gt;), and the agent host reads it with &lt;code&gt;PJSIP_HEADER(read,X-vici-cid)&lt;/code&gt;. For one real call after the fix, the IDs line up like this (values shortened):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Where&lt;/th&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ViciDial &lt;code&gt;vicidial_log_extended&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;caller_code&lt;/code&gt; / &lt;code&gt;uniqueid&lt;/code&gt; / &lt;code&gt;lead_id&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;V917…174&lt;/code&gt; / &lt;code&gt;…793.36&lt;/code&gt; / &lt;code&gt;…174&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent-host CDR&lt;/td&gt;
&lt;td&gt;&lt;code&gt;userfield&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;`\&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent-host CDR&lt;/td&gt;
&lt;td&gt;{% raw %}&lt;code&gt;uniqueid&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;…818.512&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent-host CDR&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;lastdata&lt;/code&gt; (AudioSocket UUID)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d26f…6beb&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway &lt;code&gt;session_prepared&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;callSessionId&lt;/code&gt;, &lt;code&gt;asteriskUuid&lt;/code&gt;, &lt;code&gt;externalProviderRef&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;sessionId&amp;gt;&lt;/code&gt;, &lt;code&gt;d26f…6beb&lt;/code&gt;, &lt;code&gt;…818.512&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two uniqueids differ. The caller code in the header and the CDR links them.&lt;/p&gt;

&lt;p&gt;Two more blockers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No durable outcome.&lt;/strong&gt; The application's lead table held &lt;strong&gt;0 records&lt;/strong&gt; across 229 agent conversations (test calls included). The code synced leads only when inline state parsing succeeded. That path was switched off, so the deferred extractor saved state but never created a lead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The release gate published first and tested second.&lt;/strong&gt; The deploy script switched the live spec before it ran the text gate. The gate also skipped any sentence that contained a negation. It flagged "Le ho inviato il messaggio" ("I've sent you the message", a false claim of action) but missed "Non si preoccupi, le ho inviato il messaggio" ("Don't worry, I've sent you the message").&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How did latency hold up with several calls at once?
&lt;/h2&gt;

&lt;p&gt;Badly. Single post-deploy canary calls got their first reply in 1.7 to 2.2 s. We then ran six synthetic AudioSocket conversations at once, with Ollama set to three parallel slots. The time from the caller finishing a sentence to the first agent audio was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4893, 5011, 5020, 8257, 9051, 9320 ms   -&amp;gt; median 6.64 s, worst 9.32 s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cutting the reply cap from 140 to 90 tokens gave a median of 5.27 s and a worst case of 8.60 s. That is one small run per setting, so treat it as a direction, not a percentile. The gateway advertised 10 calls and the upstream route allowed 6. Nobody had measured whether either limit could hold acceptable latency.&lt;/p&gt;

&lt;p&gt;The cache matters too: an uncached five-session run hit 19.3 s, while a warm ten-session run with identical caller text topped out at 4.85 s.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; benchmark several concurrency levels with varied caller audio and a cold cache. Set admission control from the measured p95. Send overflow to a real destination, not to &lt;code&gt;Congestion&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you keep ViciDial AMD in front of an AI agent?
&lt;/h2&gt;

&lt;p&gt;For us, no. The campaign used a ViciDial survey extension that runs Asterisk's &lt;code&gt;AMD()&lt;/code&gt; before routing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="m"&gt;8373&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Playback&lt;/span&gt;(&lt;span class="n"&gt;sip&lt;/span&gt;-&lt;span class="n"&gt;silence&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="m"&gt;8373&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;AMD&lt;/span&gt;(&lt;span class="m"&gt;2000&lt;/span&gt;,&lt;span class="m"&gt;2000&lt;/span&gt;,&lt;span class="m"&gt;1000&lt;/span&gt;,&lt;span class="m"&gt;5000&lt;/span&gt;,&lt;span class="m"&gt;120&lt;/span&gt;,&lt;span class="m"&gt;50&lt;/span&gt;,&lt;span class="m"&gt;4&lt;/span&gt;,&lt;span class="m"&gt;256&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="m"&gt;8373&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;AGI&lt;/span&gt;(&lt;span class="n"&gt;VD_amd&lt;/span&gt;.&lt;span class="n"&gt;agi&lt;/span&gt;,${&lt;span class="n"&gt;EXTEN&lt;/span&gt;})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;VD_amd.agi&lt;/code&gt; copies the &lt;code&gt;AMDSTATS&lt;/code&gt; channel variable into &lt;code&gt;run_time&lt;/code&gt;, in milliseconds. Stock Asterisk AMD doesn't set &lt;code&gt;AMDSTATS&lt;/code&gt;. ViciDial's patched &lt;code&gt;-vici&lt;/code&gt; build does, and on our dialer every row in the window had it populated. The script writes to &lt;code&gt;vicidial_amd_log&lt;/code&gt; only when &lt;code&gt;$AMD_LOG&lt;/code&gt; is 2 or 3. Ours was 3. Check both before you rely on this query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AMDSTATUS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AMDRESPONSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniqueid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run_time&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;avg_s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run_time&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;max_s&lt;/span&gt;   &lt;span class="c1"&gt;-- run_time is VARCHAR&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;uniqueid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AMDSTATUS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AMDRESPONSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run_time&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_amd_log&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2026-09-14 15:00:00'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2026-09-14 16:05:50'&lt;/span&gt;
      &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
      &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;uniqueid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AMDSTATUS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;AMDRESPONSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;run_time&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_amd_log_archive&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2026-09-14 15:00:00'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2026-09-14 16:05:50'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniqueid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniqueid&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'YOUR_AI_CAMPAIGN'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner join drops AMD rows with no matching &lt;code&gt;vicidial_log&lt;/code&gt; row (25 in this window, most from other campaigns). It produced 168 rows for 168 distinct calls in the hour before the change:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AMD result&lt;/th&gt;
&lt;th&gt;ViciDial status&lt;/th&gt;
&lt;th&gt;Calls&lt;/th&gt;
&lt;th&gt;Avg AMD run time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HUMAN&lt;/td&gt;
&lt;td&gt;sent to call menu (SVYCLM)&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;3.17 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NOTSURE / TOOLONG&lt;/td&gt;
&lt;td&gt;sent to call menu (SVYCLM)&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;3.31 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NOTSURE / NOAUDIODATA&lt;/td&gt;
&lt;td&gt;sent to call menu (SVYCLM)&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;4.80 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NOTSURE / TOOLONG&lt;/td&gt;
&lt;td&gt;dropped (PDROP)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2.16 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MACHINE (initial silence, long greeting, max words)&lt;/td&gt;
&lt;td&gt;hung up (AA)&lt;/td&gt;
&lt;td&gt;79&lt;/td&gt;
&lt;td&gt;3.25 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 88 calls sent on to the call menu spent an average of 3.50 s in AMD (maximum 5.02 s) first. 79 of 168 (47%) were hung up as machines. A person who pauses or says more than "Pronto?" can trip any of the three machine causes. We didn't listen to the AA calls, so the false-positive share is unknown.&lt;/p&gt;

&lt;p&gt;We switched the campaign to the same survey extension without AMD (&lt;code&gt;8366&lt;/code&gt;) and compared the gateway's own traces for each window. The gateway count (84) uses a slightly different window from the SQL above:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Window&lt;/th&gt;
&lt;th&gt;Calls reaching agent&lt;/th&gt;
&lt;th&gt;Caller said any words&lt;/th&gt;
&lt;th&gt;Ended &amp;lt; 9 s, no speech&lt;/th&gt;
&lt;th&gt;Voicemails caught by gateway&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;With AMD (15:10 to 16:05)&lt;/td&gt;
&lt;td&gt;84&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;td&gt;74%&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Without AMD (16:05 to 16:25)&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;30%&lt;/td&gt;
&lt;td&gt;43%&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is a before/after observation, not a randomized test. The windows differ in length and time of day, and the lead mix may differ. In this sample, more callers spoke after AMD came out. The cost is that answering machines now reach the agent. The gateway caught 9 of them in that window with an Italian transcript regex ("dopo il segnale acustico", "non è raggiungibile", mailbox menus). We don't know how many it missed. Any it misses run until the idle timeout and hold a call slot.&lt;/p&gt;

&lt;p&gt;We also tested one off-the-shelf audio AMD checkpoint, a whisper-tiny telephony classifier. We ran it offline on 15 calls from that day. The input was the first 3 s of the caller channel after the first audible sample, resampled to 16 kHz, and we took the top class. The reference labels came from the gateway traces, not from listening: a voicemail detection meant "machine", two or more substantive transcripts meant "human".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;labelled n= 15
HUMAN recall: 3/4 = 75%
MACHINE recall: 0/11 = 0%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the checkpoint agreed with 0 of 11 trace-labelled machines, calling all of them human. We didn't validate the labels by ear or establish why it disagreed, so treat this as a reason to test any AMD model on your own labelled calls before trusting it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when the prompt outgrows the context window?
&lt;/h2&gt;

&lt;p&gt;The always-on &lt;code&gt;responseConstraints&lt;/code&gt; block in the agent spec kept growing as we added rules to discipline the 8B model. It went from 6,534 to 14,268 characters between two spec versions, with &lt;code&gt;num_ctx&lt;/code&gt; at 4096. After that release, calls felt slower and replies drifted.&lt;/p&gt;

&lt;p&gt;To test the prompt in isolation, we ran an A/B probe against the same model. Knowledge, history and question were identical. Only the constraints block changed. Each variant got one warm-up request, then four sequential requests, and we report the medians:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[v34] prompt_tok=4095  prefill_ms=1609  out_tok~140  decode_ms=3436  TOTAL_ms=5335
[v35] prompt_tok=2243  prefill_ms=22    out_tok~71   decode_ms=1474  TOTAL_ms=1660
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A prompt count of 4095 against a 4096 context strongly suggests the prompt was being cut. The long variant's median output also hit the 140-token cap. In this probe, compacting the block to 5,834 characters cut the median total from 5.3 s to 1.7 s.&lt;/p&gt;

&lt;p&gt;The problem came back. Under a newer spec, long calls pushed prompts past 4096 again. Ollama logs a warning each time it truncates. To count them for one UTC day (&lt;code&gt;ollama&lt;/code&gt; is your container name):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker logs &lt;span class="nt"&gt;--since&lt;/span&gt; 2026-09-17T00:00:00Z &lt;span class="nt"&gt;--until&lt;/span&gt; 2026-09-18T00:00:00Z ollama &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /tmp/ollama-day.log 2&amp;gt;&amp;amp;1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"log fetch failed"&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"truncating input prompt"&lt;/span&gt; /tmp/ollama-day.log
&lt;span class="c"&gt;# 78&lt;/span&gt;
&lt;span class="c"&gt;# msg="truncating input prompt" limit=4096 prompt=4173 keep=4 new=4095&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On that same day, callers heard replies such as "Sembra che il cliente abbia risposto al telefono... posso continuare seguendo le istruzioni fornite" ("It seems the customer has answered the phone... I can continue following the instructions provided"). Another offered to "fare una simulazione della conversazione" ("run a simulation of the conversation"). We didn't match each of these replies to a specific truncated request. The pattern fits a model that has lost part of its instructions, but treat that link as a hypothesis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; budget tokens in code. Keep the fully rendered prompt plus a reserve for the reply under the context window. Alert on the daily truncation count, because the runtime doesn't fail. It just quietly gets worse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Are prompt rules enough to keep an 8B model safe?
&lt;/h2&gt;

&lt;p&gt;No. The clearest example was a clinical rule: never suggest full-arch treatment ("All-on-4", "riabilitazione dell'arcata") to someone who is missing only one or two teeth. The rule was in the prompt, and one run of the adversarial eval still produced this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Caller: "Mi mancano due denti davanti." ("I'm missing two front teeth.")&lt;br&gt;
Agent: "...La soluzione per te potrebbe essere l'All-on-4 o All-on-6..." ("...The solution for you could be All-on-4 or All-on-6...")&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a scripted golden conversation, the caller said "A me mancano due denti" ("I'm missing two teeth") and the model answered "visto che mancano tutti i denti" ("since all your teeth are missing").&lt;/p&gt;

&lt;p&gt;The fix was deterministic code. A per-turn eligibility scope classifies the caller's own words as "few" or "many" missing teeth. When the answer is "few", full-arch FAQs are dropped from retrieval and a speech guard scrubs full-arch phrases from the output. Here is an excerpt (not a complete program) of the detection patterns and test cases, from the Python mirror of the production C# code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# excerpt: FEWMISS = "few teeth missing"; SOLO and CNT are small helper patterns
&lt;/span&gt;&lt;span class="n"&gt;FEWMISS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manca(?:no)?\s+&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;SOLO&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;CNT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;?\s*(?:dent|incisiv|molar|canin|premolar)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;|\bmi\s+manca\b|\bme\s+ne\s+manca(?:no)?\b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="c1"&gt;# ... more alternatives omitted ...
&lt;/span&gt;    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IGNORECASE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;DETECTION_CASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mi mancano due denti&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;non sono senza denti, me ne mancano due&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ho solo due denti&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mi sono rimasti due denti&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ho perso tutti i denti&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in realtà me ne mancano due&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;# ... 20 more ...
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The full regression corpus has 25 detection cases and 41 output-scrub cases, and all 66 pass. It tests a Python mirror of the production C# patterns, not every streamed chunk boundary, cache hit and fallback in the deployed build. That needs integration tests against the deployed version. A reviewer also found a phrasing the corpus misses: the mirror classifies "non ho solo due denti, me ne mancano due" ("I don't have only two teeth, I'm missing two") as near-total loss. Regex guards are only as good as their adversarial cases. After the guard went in, the same eval answered "impianti singoli o un piccolo ponte" ("single implants or a small bridge").&lt;/p&gt;

&lt;p&gt;The same went for formal address ("Lei"), hallucinated phone numbers and meta-replies: the prompt rule came first and failed in testing, then code guards replaced it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can a safety guard break the business goal?
&lt;/h2&gt;

&lt;p&gt;Yes, and nothing alerts you when it happens. The goal was to give interested callers the WhatsApp number. A guard stopped the model from giving the number before the caller agreed. At the time, the affirmative branch was a short full-match allowlist ("sì", "va bene", "d'accordo", "certo", "ok"), plus exceptions for explicit requests such as "mi dia il numero". Real callers answered the invite with things like "Va bene, ci penso e poi ve la mando. Di dove siete?" ("OK, I'll think about it and send it. Where are you based?"), which failed the match. The guard then deleted the model's number sentence.&lt;/p&gt;

&lt;p&gt;Our proxy was the number of calls in which an &lt;code&gt;agent_reply&lt;/code&gt; event contained the spelled-out country-code prefix of the number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2026-09-08: 2 of 86       2026-09-16: 0 of 1049  (guard stripped it in 17 calls)
2026-09-09: 1 of 380      2026-09-17: 1 of 1001  (stripped in 16)
2026-09-11: 1 of 386      2026-09-21: 1 of 880
2026-09-15: 0 of 778      2026-09-22: 2 of 1300
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a text proxy, not proof of what the caller heard. In the one Sep 17 call where the number appeared, playback events show it came out garbled and the caller interrupted. For more than two weeks, the main conversion step barely appeared even in the logged text, while call volumes looked healthy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; every guard needs a counter. Measure the conversion step from playback events, then spot-check against recordings.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do real calls reveal that test calls don't?
&lt;/h2&gt;

&lt;p&gt;We reviewed one full day of traces (1,001 calls, 385 with any transcribed caller speech) and found failure modes that no synthetic test had produced.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stuck VAD on stalled audio.&lt;/strong&gt; 30 calls ran past 60 s with no transcribed caller speech. Every one had an unmatched &lt;code&gt;vad_start&lt;/code&gt; and no idle reprompt, and ended when the AudioSocket peer closed, at about 185 s or about 315 s. Together they took roughly 99 gateway-session minutes. We didn't check CDRs for how much of that was billed. The cause was in the gateway code: the VAD force-end only ran inside the per-frame audio handler. When AudioSocket frames stopped arriving mid-utterance, &lt;code&gt;in_speech&lt;/code&gt; stayed true, and that blocked the idle and max-duration timers. Here is a filter for candidate calls, written for our trace schema (one JSONL file per call, &lt;code&gt;rel&lt;/code&gt; = seconds since the call started):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;TRACE_DIR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# e.g. ./traces 20260917
&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;TRACE_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DAY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-*.jsonl&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
    &lt;span class="n"&gt;heard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vad_open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reprompted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;end_reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;fh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="n"&gt;heard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vad_start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;vad_open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vad_end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;vad_open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;idle_reprompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;reprompted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;call_end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hangupReason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;heard&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;vad_open&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;reprompted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;end_reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix belongs in a watchdog outside the audio callback. If &lt;code&gt;in_speech&lt;/code&gt; is set and no frame has arrived for a few seconds, close the utterance so the normal idle reprompt and goodbye logic can run. Keep that separate from any decision to hang up, and keep an absolute call deadline as well. Don't hang up just because media went quiet: a legitimate call can be silent, and some endpoints use silence suppression.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IVR loops.&lt;/strong&gt; The backend's transfer intent matched "prema 1" ("press 1"). So did a restaurant's phone menu, and the agent answered it with the same canned line 24 times. Cap identical consecutive replies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Losing people at the greeting.&lt;/strong&gt; In the first two days of real calls (37 calls), 15 callers hung up during the greeting without saying a word. All 5 callers whose first sentence named a concrete need (a quote, a denture, implants, yellow teeth, a full set of teeth) were tagged as leads in our review. That is a small sample, and stating a need naturally predicts a lead tag. We didn't test what caused the early hangups. Our working hypothesis is the opening, and it needs a controlled test.&lt;/p&gt;

&lt;p&gt;"Grazie a tutti" was also the first transcribed "utterance" in three of those calls, a known Whisper output on silence. Discard such phrases only when VAD or no-speech evidence agrees, since a person can really say them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does RAG help a small model on the phone?
&lt;/h2&gt;

&lt;p&gt;Less than we assumed. We replayed 292 real caller utterances offline: 241 from this agent and 51 from the hosted platform described below. The replay used the live embedder (nomic-embed-text), a read-only search of the live Qdrant collection, and a copy of the production ranking logic. For the 136 utterances that were real questions or objections, we hand-labelled the correct FAQ:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Top-1 correct, live query (prev. turn + utterance)&lt;/td&gt;
&lt;td&gt;54 / 136 (40%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top-1 correct, raw utterance only&lt;/td&gt;
&lt;td&gt;68 / 136 (50%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;None of the top 3 correct&lt;/td&gt;
&lt;td&gt;57 / 136 (42%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Top-1 correct, by source (live query)&lt;/td&gt;
&lt;td&gt;this agent 47/107, hosted 7/29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lowest top-1 / third-ranked cosine, all 292&lt;/td&gt;
&lt;td&gt;0.66 / 0.62&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The live config had &lt;code&gt;contextMinScore: 0.52&lt;/code&gt;. Even the third-ranked candidate never scored below 0.62, so that cut-off removed nothing in this replay. Prepending the previous caller turn ("contextual" query) cost about 10 points of top-1 accuracy compared with the raw utterance. Correct and wrong top-1 hits had overlapping scores, so a higher cut-off alone discards correct answers too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; replay labelled, real utterances against your index before you trust it. Pick a threshold by measuring accuracy, coverage and false rejections on those labels, and allow retrieval to return nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was a hosted no-code voice platform the answer?
&lt;/h2&gt;

&lt;p&gt;We ran a hosted no-code voice-agent platform in parallel on cold outbound calls, with a 52-node conversation flow. From its own call export over about ten days:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Real outbound dials&lt;/td&gt;
&lt;td&gt;12,160&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Connected&lt;/td&gt;
&lt;td&gt;4,285&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Calls where a human spoke (platform label)&lt;/td&gt;
&lt;td&gt;2,363&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Of those, left during the intro (platform label)&lt;/td&gt;
&lt;td&gt;2,156 (91%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tagged "warm"&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tagged "agreed to send photos"&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total platform cost&lt;/td&gt;
&lt;td&gt;about $266&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The labels come from the platform's own LLM post-call analysis, and we had no separate downstream outcome data. Like our agent, it lost most callers early. Testing a different opening is the next experiment. Nothing here shows whether the opening matters more than the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does a production readiness checklist look like?
&lt;/h2&gt;

&lt;p&gt;Each item comes from a failure above.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Call identity and integration&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] AGI input parsed as a &lt;code&gt;key: value&lt;/code&gt; protocol, never &lt;code&gt;export&lt;/code&gt;ed or &lt;code&gt;eval&lt;/code&gt;ed. Tested offline with real AGI headers.&lt;/li&gt;
&lt;li&gt;[ ] A unique AudioSocket UUID per call. A documented mapping from dialer uniqueid to agent-host uniqueid to session to recording, checked on real calls.&lt;/li&gt;
&lt;li&gt;[ ] A timeout around the whole staging step, including input and error handling. On failure, the dialplan goes to an approved human or callback destination.&lt;/li&gt;
&lt;li&gt;[ ] A consenting, qualified test call creates exactly one lead/task row in the application database. Separately, test that a refusal and an opt-out are stored correctly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Capacity and latency&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Concurrency measured at several levels with a cold cache and varied caller audio, reporting p50 and p95 from the caller's end of speech to the first agent audio.&lt;/li&gt;
&lt;li&gt;[ ] Admission limit set from that measurement. Overflow goes to a human queue or a callback, not &lt;code&gt;Congestion&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dialer front end&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] AMD run time measured from &lt;code&gt;vicidial_amd_log.run_time&lt;/code&gt;, after confirming that &lt;code&gt;AMDSTATS&lt;/code&gt; is populated and DB logging is on.&lt;/li&gt;
&lt;li&gt;[ ] If AMD stays on, false positives sampled by listening to AA recordings.&lt;/li&gt;
&lt;li&gt;[ ] Voicemail detection validated on labelled calls in your own language.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;LLM and prompt&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Worst-case rendered prompt (long call, injected FAQs, full history) plus a reply reserve fits under &lt;code&gt;num_ctx&lt;/code&gt;, enforced in code.&lt;/li&gt;
&lt;li&gt;[ ] Daily count of "truncating input prompt" warnings, with an alert.&lt;/li&gt;
&lt;li&gt;[ ] Safety rules that matter have deterministic guards, a regression corpus, and integration tests on the deployed build, including streamed output.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Guards and funnel&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every guard logs when it fires, and someone reviews the counts daily.&lt;/li&gt;
&lt;li&gt;[ ] The conversion step measured from playback events and spot-checked against recordings.&lt;/li&gt;
&lt;li&gt;[ ] When a guard empties a reply, a spoken fallback line plays instead of silence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real-call hygiene&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] A watchdog outside the audio callback closes stuck utterances. Hangup policy is separate and tested with silent callers.&lt;/li&gt;
&lt;li&gt;[ ] Identical consecutive replies capped, to break IVR loops.&lt;/li&gt;
&lt;li&gt;[ ] Suspected Whisper silence hallucinations discarded only when VAD/no-speech evidence agrees.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Retrieval&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Hit@1 measured on real, labelled caller utterances.&lt;/li&gt;
&lt;li&gt;[ ] Threshold chosen on labelled accuracy and false rejections. Retrieval can return zero results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Release&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] The candidate is gated before it takes traffic, with a manifest (spec, KB, code, config, models) and a rehearsed rollback.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What would I do differently?
&lt;/h2&gt;

&lt;p&gt;I'd build the measurement before the agent: per-call identity, a playback-based funnel, guard counters and a concurrency benchmark. Demos and service dashboards missed nearly every problem here. Targeted tests and real-call reviews exposed them.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sip</category>
      <category>voip</category>
    </item>
    <item>
      <title>Detecting False Answer Supervision (FAS) Carrier Fraud</title>
      <dc:creator>Moisi Trungu</dc:creator>
      <pubDate>Sun, 11 Oct 2026 03:30:03 +0000</pubDate>
      <link>https://dev.to/moisi_trungu_31647b7ac300/detecting-false-answer-supervision-fas-carrier-fraud-2kc0</link>
      <guid>https://dev.to/moisi_trungu_31647b7ac300/detecting-false-answer-supervision-fas-carrier-fraud-2kc0</guid>
      <description>&lt;h1&gt;
  
  
  Detecting False Answer Supervision (FAS) Carrier Fraud
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;False Answer Supervision means your carrier answers calls that no human ever picked up, then bills you for them. You can catch it with CDR queries, a duration histogram, and one SIP capture. I hunt for it on a production campaign where two carriers run a 50/50 split on identical traffic, so every metric below comes with a built-in control group.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tested on: ViciDial 3.x, Asterisk 13-18, MariaDB 10.x, Homer 7.10. The SQL targets ViciDial's &lt;code&gt;vicidial_log&lt;/code&gt;, but the method works on any Asterisk CDR table.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is False Answer Supervision?
&lt;/h2&gt;

&lt;p&gt;FAS is when a carrier (or someone upstream of them) sends &lt;code&gt;200 OK&lt;/code&gt; on a call that was never really answered. Your switch starts the billing clock, your dialer marks the call answered, and the money goes to whoever faked the answer signal.&lt;/p&gt;

&lt;p&gt;It comes in several flavors, and I've seen most of them on real traffic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pure FAS&lt;/strong&gt;: &lt;code&gt;200 OK&lt;/code&gt; arrives before the callee's phone even rings. Your IVR plays to nobody.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Early-media FAS&lt;/strong&gt;: the carrier answers, then plays ringback tone &lt;em&gt;on the RTP stream&lt;/em&gt;. Your AMD hears ringing after "answer" and misclassifies wildly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Short FAS&lt;/strong&gt;: answer, a few seconds of silence or a fake announcement, hangup at 3-30 seconds. Small per call, huge at volume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Call stretching&lt;/strong&gt;: the callee hangs up but the carrier holds the billing leg open for extra seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voicemail simulation&lt;/strong&gt;: a slice of your traffic gets routed to fake voicemail prompts and billed at full duration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The nasty part: FAS makes your stats look &lt;em&gt;better&lt;/em&gt;. ASR and answered-call counts both climb, and nobody investigates a dashboard that just improved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is FAS so hard to spot in normal reports?
&lt;/h2&gt;

&lt;p&gt;Because every individual call record looks legitimate. The fraud only shows up in &lt;em&gt;distributions&lt;/em&gt;, and only when you have something to compare against.&lt;/p&gt;

&lt;p&gt;The single best setup for catching FAS is an A/B split: route the same traffic 50/50 across two carriers and compare them. Same destinations, same time of day, same lead lists. Any large divergence between the two sides is a carrier-side effect, not a traffic effect. If you only have one carrier, compare against your own historical baseline instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I split traffic across two carriers for comparison?
&lt;/h2&gt;

&lt;p&gt;In the Asterisk dialplan, randomize the carrier per call and log which one each call took:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;9911&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CARRIER&lt;/span&gt;=${&lt;span class="n"&gt;IF&lt;/span&gt;($[${&lt;span class="n"&gt;RAND&lt;/span&gt;(&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="m"&gt;2&lt;/span&gt;)}=&lt;span class="m"&gt;2&lt;/span&gt;]?&lt;span class="n"&gt;carrier_a&lt;/span&gt;:&lt;span class="n"&gt;carrier_b&lt;/span&gt;)})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;9911&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;System&lt;/span&gt;(/&lt;span class="n"&gt;usr&lt;/span&gt;/&lt;span class="n"&gt;local&lt;/span&gt;/&lt;span class="n"&gt;sbin&lt;/span&gt;/&lt;span class="n"&gt;log_carrier&lt;/span&gt;.&lt;span class="n"&gt;sh&lt;/span&gt; ${&lt;span class="n"&gt;UNIQUEID&lt;/span&gt;} ${&lt;span class="n"&gt;CARRIER&lt;/span&gt;})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;9911&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Dial&lt;/span&gt;(&lt;span class="n"&gt;SIP&lt;/span&gt;/${&lt;span class="n"&gt;CARRIER&lt;/span&gt;}/${&lt;span class="n"&gt;EXTEN&lt;/span&gt;:&lt;span class="m"&gt;4&lt;/span&gt;},,&lt;span class="n"&gt;tTo&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;log_carrier.sh&lt;/code&gt; is a two-line script that inserts &lt;code&gt;(uniqueid, carrier, now())&lt;/code&gt; into an attribution table, say &lt;code&gt;carrier_route_log&lt;/code&gt;. Now every CDR can be joined to the carrier that handled it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha that cost me a week:&lt;/strong&gt; on answered calls, ViciDial's &lt;code&gt;call_log&lt;/code&gt; AGI &lt;em&gt;rewrites&lt;/em&gt; &lt;code&gt;vicidial_log.uniqueid&lt;/code&gt; to the carrier leg's uniqueid. The uniqueid you captured at dial time never matches for answered calls, so your join silently loses exactly the calls you care about. Check your tag coverage per status before trusting any per-carrier number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
       &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;crl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniqueid&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;tagged&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;carrier_route_log&lt;/span&gt; &lt;span class="n"&gt;crl&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniqueid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If answered statuses show near-zero coverage, capture the carrier in a hangup handler with &lt;code&gt;CDR(dstchannel)&lt;/code&gt; instead of &lt;code&gt;${UNIQUEID}&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which CDR metrics expose FAS?
&lt;/h2&gt;

&lt;p&gt;Run a per-carrier triage card over the last few hours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
  &lt;span class="n"&gt;crl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;dialed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'AB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'ADC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'DC'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;asr_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'AB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'ADC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'DC'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length_in_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;acd_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length_in_sec&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;short_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length_in_sec&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
      &lt;span class="k"&gt;NULLIF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NA'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'AB'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;fasr&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;carrier_route_log&lt;/span&gt; &lt;span class="n"&gt;crl&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt; &lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uniqueid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;crl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How to read it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Clean route&lt;/th&gt;
&lt;th&gt;FAS suspect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ASR delta between carriers&lt;/td&gt;
&lt;td&gt;under ~10%&lt;/td&gt;
&lt;td&gt;over 30% on identical traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACD on answered calls&lt;/td&gt;
&lt;td&gt;stable, similar both sides&lt;/td&gt;
&lt;td&gt;one side several seconds shorter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FASR (short calls ÷ failed calls)&lt;/td&gt;
&lt;td&gt;under 0.30&lt;/td&gt;
&lt;td&gt;over 0.30; over 1.0 is near-certain&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The FASR ratio (short answered calls divided by no-answers) comes from a Datanomers fraud-detection patent. A carrier faking answers converts would-be no-answers into short billed calls, so the ratio climbs on the dirty route while staying flat on the clean one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does a FAS duration histogram look like?
&lt;/h2&gt;

&lt;p&gt;FAS rigs cut calls at fixed points. Humans don't. Pull a per-second histogram of answered call durations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; USER &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-BN&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
SELECT length_in_sec, COUNT(*)
FROM vicidial_log
WHERE call_date &amp;gt;= NOW() - INTERVAL 6 HOUR
  AND status NOT IN ('NA','AB','ADC','DC')
  AND length_in_sec BETWEEN 1 AND 60
GROUP BY length_in_sec;"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{a[$1]=$2; if($2&amp;gt;m)m=$2} END{for(s=1;s&amp;lt;=60;s++){n=a[s]+0;
  printf "%2ds %5d ",s,n; for(i=0;i&amp;lt;40*n/(m+1);i++)printf "#"; print ""}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An isolated spike at 28, 30, 45, or 60 seconds with empty neighbors is a cutoff, not human behavior.&lt;/p&gt;

&lt;p&gt;One warning before you accuse anyone: know your &lt;em&gt;legitimate&lt;/em&gt; peaks. On an IVR campaign, every no-input call lands at exactly &lt;code&gt;audio length + menu timeout&lt;/code&gt;. My survey audio ran 21.7 seconds with a 6-second DTMF window, so a huge honest spike sat at 28 seconds. I verified all 1,948 calls in that bucket were normal completions before looking further. Compute your expected peak first, then investigate everything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I confirm FAS at the SIP level?
&lt;/h2&gt;

&lt;p&gt;Two signatures, both visible in Homer (or any HEP capture, or even &lt;code&gt;sngrep&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Post-dial delay that's too fast.&lt;/strong&gt; PDD is the time from &lt;code&gt;INVITE&lt;/code&gt; to &lt;code&gt;200 OK&lt;/code&gt;. Real mobile calls in Europe take 3-6 seconds to answer at minimum: the network has to page the handset and the human has to react. A carrier answering in under 1 second is structurally impossible without FAS. Query your HEP database for per-destination-IP PDD percentiles and count sub-1-second answers. More than ~5% sub-1s on one carrier IP is a red flag.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;200 OK&lt;/code&gt; with no ringing phase.&lt;/strong&gt; A real call almost always shows &lt;code&gt;INVITE → 180/183 → 200&lt;/code&gt;. Pure FAS often goes straight &lt;code&gt;INVITE → 200&lt;/code&gt;. A small baseline is normal (some carriers only send &lt;code&gt;100 Trying&lt;/code&gt;), but if more than 10% of one carrier's calls skip ringing entirely, that route deserves a hard look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can I hear FAS in the recordings?
&lt;/h2&gt;

&lt;p&gt;Yes, and it's the most convincing evidence you can attach to a dispute. Record the inbound leg (&lt;code&gt;-in.wav&lt;/code&gt; in Asterisk's monitor directory) and check what the carrier actually delivered after "answer":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;
&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;leg-in.wav&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getframerate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getnframes&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frombuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readframes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;int16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;sp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rfft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="n"&gt;fr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rfftfreq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getframerate&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;band&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="mi"&gt;480&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RMS=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rms&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  peak=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;fr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;argmax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;Hz  ringband=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;band&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RMS under 50&lt;/strong&gt;: silence. Normal for an IVR call where the callee just listens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RMS over 500 with 25%+ energy at 400-480 Hz&lt;/strong&gt;: that's a ringback tone &lt;em&gt;after&lt;/em&gt; answer. Early-media FAS, caught on tape.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RMS over 500 with speech-band content&lt;/strong&gt; on calls your agents never spoke on: a carrier announcement or bot loop, billed as conversation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Bonus signal for IVR campaigns: DTMF response rate per carrier. Fake answers don't press 1. If one carrier's press-through rate is a fraction of its twin's on identical traffic, the "answered" calls on that route have nobody on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the definitive FAS test?
&lt;/h2&gt;

&lt;p&gt;Dial a number that cannot answer. A confirmed-unallocated number must return &lt;code&gt;404 Not Found&lt;/code&gt; or &lt;code&gt;484 Address Incomplete&lt;/code&gt;. If the carrier returns &lt;code&gt;200 OK&lt;/code&gt;, that is conclusive. There is no innocent explanation for answering a number that doesn't exist.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s1"&gt;'sip set debug ip 203.0.113.10'&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s1"&gt;'originate SIP/carrier_a/UNALLOCATED_NUMBER application Hangup'&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s1"&gt;'sip set debug off'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a number you control or one your national numbering plan documents as unassigned. One probe per carrier is enough; this is a diagnostic, not a load test. For continuous unattended probing, &lt;code&gt;voip_patrol&lt;/code&gt; runs scripted test calls from cron.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do I do once a carrier is confirmed dirty?
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Stop the bleeding. Reweight the dialplan &lt;code&gt;RAND()&lt;/code&gt; to send 100% to the clean carrier, or set the bad carrier inactive. On ViciDial, edit the carrier's dialplan entry in the admin GUI so the change survives config regeneration.&lt;/li&gt;
&lt;li&gt;Build the evidence pack: the triage card, the histogram, the PDD stats, one SIP ladder showing &lt;code&gt;INVITE → 200&lt;/code&gt; with no ringing, one recording of post-answer ringback, and the unallocated-number probe. That combination is very hard to argue with.&lt;/li&gt;
&lt;li&gt;Dispute the invoice. Carriers know exactly what FAS is. A billing dispute backed by SIP captures usually gets credited quietly. If it doesn't, you already stopped routing to them.&lt;/li&gt;
&lt;li&gt;Keep the A/B split running. FAS is rarely constant. It appears on specific destination prefixes at specific times. A permanent 90/10 split with the triage card on a cron is cheap insurance.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does high ASR always mean FAS?&lt;/strong&gt; No. A carrier with better routes genuinely completes more calls. FAS shows as high ASR &lt;em&gt;combined with&lt;/em&gt; short ACD, high FASR, impossible PDD, and low human-response signals. One metric is a hint; four together is a case.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can FAS happen on inbound?&lt;/strong&gt; The common inbound equivalent is call stretching, meaning duration padding after hangup. Compare your CDR durations against the far end's records if you suspect it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My carrier blames their upstream. Does that matter?&lt;/strong&gt; Not to you. You bought termination from them; where in their chain the fraud happens is their problem. The evidence pack works either way.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sip</category>
      <category>voip</category>
    </item>
    <item>
      <title>How to Configure ViciDial Closer Campaigns &amp; In-Groups</title>
      <dc:creator>Moisi Trungu</dc:creator>
      <pubDate>Sat, 10 Oct 2026 03:30:04 +0000</pubDate>
      <link>https://dev.to/moisi_trungu_31647b7ac300/how-to-configure-vicidial-closer-campaigns-in-groups-2759</link>
      <guid>https://dev.to/moisi_trungu_31647b7ac300/how-to-configure-vicidial-closer-campaigns-in-groups-2759</guid>
      <description>&lt;h1&gt;
  
  
  How to Configure ViciDial Closer Campaigns &amp;amp; In-Groups
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Master the complete process of setting up closer campaigns and in-groups in ViciDial, from database configuration through agent assignment and real-time monitoring.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before proceeding with closer campaign and in-group configuration, ensure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ViciDial installation&lt;/strong&gt; (3.14+ recommended) with working Asterisk (16+)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database access&lt;/strong&gt;: Direct MySQL/MariaDB access to the &lt;code&gt;asterisk&lt;/code&gt; database&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Admin credentials&lt;/strong&gt;: Access to &lt;code&gt;/vicidial/admin.php&lt;/code&gt; with administrator role&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asterisk CLI access&lt;/strong&gt;: Ability to run &lt;code&gt;asterisk -rx&lt;/code&gt; commands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing dialplans&lt;/strong&gt;: At least one active dialer campaign configured&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User accounts&lt;/strong&gt;: Multiple agents already created in ViciDial system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-group defined&lt;/strong&gt;: A primary in-group created before closer assignment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSH/Shell access&lt;/strong&gt; to ViciDial server with sudo privileges&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding Closer Campaigns vs In-Groups
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Are Closer Campaigns?
&lt;/h3&gt;

&lt;p&gt;Closer campaigns are ViciDial's mechanism for transferring live inbound calls or callbacks to specific agents who specialize in closing sales. Unlike dialer campaigns that push calls outbound, closer campaigns pull inbound calls into a queue for assignment to available agents.&lt;/p&gt;

&lt;p&gt;Key characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receive transferred calls from dialer campaigns or direct phone numbers&lt;/li&gt;
&lt;li&gt;Queue calls until agent availability&lt;/li&gt;
&lt;li&gt;Track transfer times and call dispositions separately&lt;/li&gt;
&lt;li&gt;Support multiple in-groups within one closer campaign&lt;/li&gt;
&lt;li&gt;Integrate with DNC (Do Not Call) lists and custom call rules&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Are In-Groups?
&lt;/h3&gt;

&lt;p&gt;In-groups are the actual agent queues where calls wait for assignment. A closer campaign may have multiple in-groups (e.g., "Sales", "Support", "Escalations"), each with distinct:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Member agents with individual priorities&lt;/li&gt;
&lt;li&gt;Call handling rules and routing logic&lt;/li&gt;
&lt;li&gt;Recording preferences&lt;/li&gt;
&lt;li&gt;Custom CID masking&lt;/li&gt;
&lt;li&gt;Hold music and announcements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The relationship: &lt;strong&gt;Closer Campaign contains In-Groups containing Agents&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: Create the Closer Campaign via Web UI
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1.1: Access Campaign Configuration
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log into &lt;code&gt;/vicidial/admin.php&lt;/code&gt; with administrator account&lt;/li&gt;
&lt;li&gt;Navigate: &lt;strong&gt;Admin → Campaigns → Add New Campaign&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Campaign Type&lt;/strong&gt;: "Closer"&lt;/li&gt;
&lt;li&gt;Fill in campaign details:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Example Value&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Campaign ID&lt;/td&gt;
&lt;td&gt;CLOSER_SALES&lt;/td&gt;
&lt;td&gt;Uppercase, no spaces, 2-20 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Campaign Name&lt;/td&gt;
&lt;td&gt;Sales Closer Campaign&lt;/td&gt;
&lt;td&gt;Human-readable name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;Enable immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outbound CID&lt;/td&gt;
&lt;td&gt;18005551234&lt;/td&gt;
&lt;td&gt;Caller ID for transfers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outbound CID Name&lt;/td&gt;
&lt;td&gt;Sales Dept&lt;/td&gt;
&lt;td&gt;Display name (optional)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phone Number&lt;/td&gt;
&lt;td&gt;18005551000&lt;/td&gt;
&lt;td&gt;DID for external transfers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Call Time Zone&lt;/td&gt;
&lt;td&gt;America/Chicago&lt;/td&gt;
&lt;td&gt;Agent's timezone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use Asterisk&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;Required for call routing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Step 1.2: Configure Callback and Transfer Settings
&lt;/h3&gt;

&lt;p&gt;In the same campaign creation form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;Callback&lt;/span&gt; &lt;span class="n"&gt;Auto&lt;/span&gt; &lt;span class="n"&gt;Hold&lt;/span&gt; &lt;span class="n"&gt;Minutes&lt;/span&gt;:          &lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="n"&gt;Callback&lt;/span&gt; &lt;span class="n"&gt;Auto&lt;/span&gt; &lt;span class="n"&gt;Dial&lt;/span&gt;:                  &lt;span class="n"&gt;YES&lt;/span&gt;
&lt;span class="n"&gt;Campaigns&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt;:           &lt;span class="n"&gt;DIALER_SALES&lt;/span&gt;,&lt;span class="n"&gt;DIALER_LEADS&lt;/span&gt;
&lt;span class="n"&gt;Transfer&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt; &lt;span class="n"&gt;Seconds&lt;/span&gt;:            &lt;span class="m"&gt;45&lt;/span&gt;
&lt;span class="n"&gt;Voicemail&lt;/span&gt; &lt;span class="n"&gt;Extension&lt;/span&gt;:                 &lt;span class="m"&gt;9999&lt;/span&gt;
&lt;span class="n"&gt;Call&lt;/span&gt; &lt;span class="n"&gt;Recording&lt;/span&gt;:                      &lt;span class="n"&gt;ALWAYS&lt;/span&gt;
&lt;span class="n"&gt;Recording&lt;/span&gt; &lt;span class="n"&gt;Call&lt;/span&gt; &lt;span class="n"&gt;Display&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;:         &lt;span class="n"&gt;CLOSER_SALES&lt;/span&gt;
&lt;span class="n"&gt;Lead&lt;/span&gt; &lt;span class="n"&gt;ID&lt;/span&gt; &lt;span class="n"&gt;Prefix&lt;/span&gt;:                      &lt;span class="n"&gt;CS&lt;/span&gt;-
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical setting&lt;/strong&gt;: Leave "Closer Dial Campaign" BLANK (this is for callbacks originated from closer agents, not for receiving transfers)&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1.3: Save and Verify
&lt;/h3&gt;

&lt;p&gt;Click &lt;strong&gt;SUBMIT&lt;/strong&gt;. ViciDial will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create campaign record in &lt;code&gt;vicidial_campaigns&lt;/code&gt; table&lt;/li&gt;
&lt;li&gt;Create campaign contexts in &lt;code&gt;/etc/asterisk/extensions-vicidial.conf&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Generate default dialplan extensions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Verify creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT campaign_id, campaign_name, campaign_type, active FROM vicidial_campaigns WHERE campaign_id='CLOSER_SALES';"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| CLOSER_SALES | Sales Closer Campaign | CLOSER | Y |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 2: Create In-Groups Within the Closer Campaign
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 2.1: In-Group Fundamentals
&lt;/h3&gt;

&lt;p&gt;In-groups are the actual call queues. You must create at least one in-group per closer campaign. Most production environments have 2-4 in-groups:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Primary in-group&lt;/strong&gt;: Handles all standard transfers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overflow in-group&lt;/strong&gt;: Secondary queue when primary is full&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VIP in-group&lt;/strong&gt;: Dedicated high-value customer queue&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escalation in-group&lt;/strong&gt;: Complex issues requiring senior agents&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2.2: Create In-Group via Database
&lt;/h3&gt;

&lt;p&gt;While ViciDial admin UI has in-group creation, direct database insertion is faster for bulk setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroups&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ingroup_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;callback_grp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;voicemail_ext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;hold_music&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;recording_call_display_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;call_timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;call_priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;full_set_queue_exten&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;customer_greeting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;agent_greeting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;in_group_digits&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'CLOSER_SALES'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Sales Main Queue'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Primary in-group for sales closer campaign'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'18005551000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'9999'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'120'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Thanks for calling. A representative will be with you shortly.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Thank you for calling. Please give me one moment while I retrieve your information.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;''&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key fields explanation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ingroup_id&lt;/code&gt;: Unique identifier across system (24 chars max)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;callback_grp&lt;/code&gt;: Allow agents to callback? Set 'Y' for closer queues&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;call_timeout&lt;/code&gt;: Seconds before call abandons from queue (120-300 typical)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;call_priority&lt;/code&gt;: Lower number = higher priority (1-5 range)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;full_set_queue_exten&lt;/code&gt;: Extension when queue full (usually extension 1)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;recording_call_display_name&lt;/code&gt;: Identifies recording in playback system&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2.3: Create Secondary In-Group (Overflow)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroups&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ingroup_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;callback_grp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;voicemail_ext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;hold_music&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;call_timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;call_priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;full_set_queue_exten&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'CLOSER_SALES_OVERFLOW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'CLOSER_SALES'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Sales Overflow Queue'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Overflow for sales closer campaign'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'18005551001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'9999'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'150'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'1'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2.4: Verify In-Group Creation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT ingroup_id, campaign_id, ingroup_name, active, call_timeout FROM vicidial_ingroups WHERE campaign_id='CLOSER_SALES';"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| CLOSER_SALES_MAIN      | CLOSER_SALES | Sales Main Queue    | Y | 120 |
| CLOSER_SALES_OVERFLOW  | CLOSER_SALES | Sales Overflow Queue| Y | 150 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 3: Assign Agents to In-Groups
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 3.1: Add Agent Memberships
&lt;/h3&gt;

&lt;p&gt;Agents must be explicitly assigned to in-groups via the &lt;code&gt;vicidial_ingroup_agents&lt;/code&gt; table. Each agent can belong to multiple in-groups with different priorities and statuses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroup_agents&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;agent_priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;agent_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;closer_flag&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'agent001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ACTIVE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'agent002'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ACTIVE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'agent003'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ACTIVE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_OVERFLOW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'agent004'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ACTIVE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_OVERFLOW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'agent002'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ACTIVE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Field explanations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;agent_priority&lt;/code&gt;: 1=highest priority, 9=lowest. Lower number gets called first&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;closer_flag&lt;/code&gt;: 'Y' = this agent handles closer calls (must be 'Y')&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;agent_status&lt;/code&gt;: 'ACTIVE'='available', 'INACTIVE'='unavailable', 'DELETED'='removed'&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lead_id&lt;/code&gt;: Usually '0' for in-group assignments; can specify a particular lead&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3.2: Configure Individual Agent Closer Permissions
&lt;/h3&gt;

&lt;p&gt;Each agent needs ViciDial user account flags enabled. Update the &lt;code&gt;vicidial_users&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; 
  &lt;span class="n"&gt;closer_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;closer_campaigns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CLOSER_SALES'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;call_transfer_flag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;no_outbound_calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;can_transfer_to_other_users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'agent001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'agent002'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'agent003'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'agent004'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Critical fields:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;closer_enabled&lt;/code&gt;: Set to '1' to allow agent to receive closer calls&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;closer_campaigns&lt;/code&gt;: Comma-separated list of campaigns agent can handle&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;call_transfer_flag&lt;/code&gt;: Enables blind/attended transfers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;can_transfer_to_other_users&lt;/code&gt;: Allows warm transfer to other agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT user_id, closer_enabled, closer_campaigns FROM vicidial_users WHERE user_id IN ('agent001','agent002','agent003','agent004');"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3.3: Set Agent Pause Codes for In-Group Work
&lt;/h3&gt;

&lt;p&gt;Create pause codes specific to closer work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_pause_codes&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;pause_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;pause_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;inbound_seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;outbound_seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;active&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Closer Call In Progress'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_BREAK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Closer Break'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_LUNCH'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Closer Lunch'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These codes appear in agent's pause dropdown in &lt;code&gt;/agc/vicidial.php&lt;/code&gt; interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: Configure Asterisk Dialplan Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 4.1: Verify Auto-Generated Extensions
&lt;/h3&gt;

&lt;p&gt;ViciDial automatically creates extensions when you add a closer campaign. Verify in &lt;code&gt;/etc/asterisk/extensions-vicidial.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 30 &lt;span class="s2"&gt;"exten =&amp;gt; 8900,1"&lt;/span&gt; /etc/asterisk/extensions-vicidial.conf | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-40&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see auto-generated closer campaign context. If missing, manually trigger regeneration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/share/astguiclient/astguiclient_closer_extension_sync.pl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4.2: Create Custom Transfer Extension (Optional)
&lt;/h3&gt;

&lt;p&gt;To allow dialer agents to transfer calls to closer campaign, add to &lt;code&gt;/etc/asterisk/extensions-vicidial.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ViciDial-Closer-Transfer]
exten =&amp;gt; 1,1,NoOp(Closer Transfer Request to ${closer_queue})
 same =&amp;gt; n,ViciDialCloserQueue(${closer_queue}|${closer_priority}|${CALLERID(num)})
 same =&amp;gt; n,Hangup()

exten =&amp;gt; i,1,NoOp(Invalid extension)
 same =&amp;gt; n,Playback(invalid)
 same =&amp;gt; n,Hangup()

exten =&amp;gt; t,1,NoOp(Transfer timeout)
 same =&amp;gt; n,Hangup()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4.3: Reload Asterisk Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"dialplan reload"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify dialplan loaded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"dialplan show ViciDial-Closer-Transfer"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ ViciDial-Closer-Transfer ]
'1' =&amp;gt;            1. NoOp(Closer Transfer Request to ${closer_queue})
                  2. ViciDialCloserQueue(${closer_queue}|${closer_priority}|${CALLERID(num)})
                  3. Hangup()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 5: Test the Closer Campaign
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 5.1: Pre-Test Verification Checklist
&lt;/h3&gt;

&lt;p&gt;Before testing, verify all components in database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Test script for closer campaign setup&lt;/span&gt;

&lt;span class="nv"&gt;CAMPAIGN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"CLOSER_SALES"&lt;/span&gt;
&lt;span class="nv"&gt;DB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"asterisk"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=== Campaign Configuration ==="&lt;/span&gt;
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT campaign_id, campaign_type, active FROM vicidial_campaigns WHERE campaign_id='&lt;/span&gt;&lt;span class="nv"&gt;$CAMPAIGN&lt;/span&gt;&lt;span class="s2"&gt;';"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;=== In-Groups ==="&lt;/span&gt;
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT ingroup_id, ingroup_name, active FROM vicidial_ingroups WHERE campaign_id='&lt;/span&gt;&lt;span class="nv"&gt;$CAMPAIGN&lt;/span&gt;&lt;span class="s2"&gt;';"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;=== Agent Assignments ==="&lt;/span&gt;
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT ingroup_id, user_id, agent_priority, closer_flag FROM vicidial_ingroup_agents WHERE ingroup_id LIKE '&lt;/span&gt;&lt;span class="nv"&gt;$CAMPAIGN&lt;/span&gt;&lt;span class="s2"&gt;%';"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;=== Agent Closer Status ==="&lt;/span&gt;
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT user_id, closer_enabled, closer_campaigns FROM vicidial_users WHERE closer_enabled='1' LIMIT 5;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x test_closer_config.sh
./test_closer_config.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5.2: Manual Agent Availability Test
&lt;/h3&gt;

&lt;p&gt;Log in agents to the system and verify they appear available for closer calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check current agent statuses&lt;/span&gt;
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT user_id, status, campaign_id, on_call FROM vicidial_users WHERE user_id IN ('agent001','agent002','agent003');"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5.3: Simulate Incoming Call to In-Group
&lt;/h3&gt;

&lt;p&gt;Use Asterisk CLI to originate test call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"channel originate SIP/2000 extension 8900@CLOSER_SALES_MAIN-inbound"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitor Asterisk logs in real-time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/asterisk/messages | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"closer&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;ingroup&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;queue"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5.4: Monitor Call Routing
&lt;/h3&gt;

&lt;p&gt;Check Asterisk queue status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"queue show CLOSER_SALES_MAIN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CLOSER_SALES_MAIN has 0 calls (max unlimited) in 'rrordered' strategy
     agent001 (SIP/2000) (paused) (Not in call, paused 0m 45s)
     agent002 (SIP/2001) (ready) (Penalty: 2)
     agent003 (SIP/2002) (ready) (Penalty: 3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5.5: Verify Call Logging
&lt;/h3&gt;

&lt;p&gt;After test call completes, verify logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
SELECT 
  call_id,
  caller_id,
  customer_id,
  campaign_id,
  start_time,
  call_duration,
  status
FROM vicidial_closer_log 
WHERE campaign_id='CLOSER_SALES' 
ORDER BY start_time DESC 
LIMIT 5;
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 6: Advanced Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 6.1: Implement Call Distribution Strategy
&lt;/h3&gt;

&lt;p&gt;Closer in-groups support multiple distribution strategies. Configure in &lt;code&gt;vicidial_ingroups&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroups&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; 
  &lt;span class="n"&gt;call_distribution_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'rrordered'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;max_concurrent_calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'5'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;force_return_to_hold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'N'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ingroup_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Distribution methods:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;rrordered&lt;/code&gt;: Round-robin by agent priority (default, recommended)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;leastrecent&lt;/code&gt;: Agent with oldest last call&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;fewestcalls&lt;/code&gt;: Agent with fewest total calls today&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;random&lt;/code&gt;: Random agent selection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 6.2: Configure Callback Rules
&lt;/h3&gt;

&lt;p&gt;Allow agents in closer campaign to schedule callbacks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; 
  &lt;span class="n"&gt;closer_dial_campaign&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CLOSER_SALES_CB'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;callback_dial_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'MANUAL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;callback_hours_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'08:00:00'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;callback_hours_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'20:00:00'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CLOSER_SALES'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This requires a separate dialer campaign (&lt;code&gt;CLOSER_SALES_CB&lt;/code&gt;) created for callback origination.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6.3: Implement DNC (Do Not Call) Filtering
&lt;/h3&gt;

&lt;p&gt;Link in-group to DNC lists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroup_dnc_filters&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;dnc_filter_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;active&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'FEDERAL_DNC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'COMPANY_DNC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'INTERNAL_DNC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6.4: Set Up Custom CID Masking
&lt;/h3&gt;

&lt;p&gt;Force specific outbound CID for transfers from this in-group:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroups&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; 
  &lt;span class="n"&gt;outbound_cid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'18005551234'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;outbound_cid_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Sales Department'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;cid_mask_method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'FORCED'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ingroup_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6.5: Configure Call Recording Exceptions
&lt;/h3&gt;

&lt;p&gt;Set custom recording preferences:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroups&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; 
  &lt;span class="n"&gt;recording_call_display_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CLOSER_SALES_MAIN_REC'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;call_recording_compression&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'32'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;call_recording_quality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'gsm'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;recording_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ingroup_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 7: Performance Tuning
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 7.1: Optimize Queue Timeout Settings
&lt;/h3&gt;

&lt;p&gt;Based on team size and call volume, adjust timeouts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroups&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; 
  &lt;span class="n"&gt;call_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'120'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;-- 2 min normal calls&lt;/span&gt;
  &lt;span class="n"&gt;callback_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'180'&lt;/span&gt;        &lt;span class="c1"&gt;-- 3 min callbacks&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ingroup_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitor actual wait times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
SELECT 
  DATE(start_time) as Date,
  COUNT(*) as Total_Calls,
  AVG(wait_time) as Avg_Wait_Sec,
  MAX(wait_time) as Max_Wait_Sec
FROM vicidial_closer_log
WHERE campaign_id='CLOSER_SALES'
  AND start_time &amp;gt; DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY DATE(start_time)
ORDER BY Date DESC;
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7.2: Monitor Agent Adherence to In-Group
&lt;/h3&gt;

&lt;p&gt;Track agent time allocation across in-groups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;calls_handled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_talk_time&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_closer_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CURDATE&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ingroup_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;total_talk_time&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7.3: Set Agent Concurrent Call Limits
&lt;/h3&gt;

&lt;p&gt;Prevent single agent from receiving multiple simultaneous calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; 
  &lt;span class="n"&gt;max_concurrent_calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;force_logout_after_seconds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'agent001'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'agent002'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'agent003'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'agent004'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 8: Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Issue: Calls Not Routing to In-Group
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom&lt;/strong&gt;: Incoming calls to DID don't reach agent queues, instead hangup or disconnect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root causes &amp;amp; solutions&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Campaign not active&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT campaign_id, active FROM vicidial_campaigns WHERE campaign_id='CLOSER_SALES';"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;active&lt;/code&gt; is 'N', update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Y'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;In-group not linked to campaign&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT ingroup_id, campaign_id FROM vicidial_ingroups WHERE campaign_id='CLOSER_SALES';"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dialplan context missing&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"dialplan show CLOSER_SALES | head -20"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If no output, regenerate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/share/astguiclient/astguiclient_extension_update.pl
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"dialplan reload"&lt;/span&gt;
&lt;span class="nb"&gt;sleep &lt;/span&gt;2
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"module reload pbx_config"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SIP routing issue&lt;/strong&gt; - verify SIP profile includes ViciDial context:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 5 &lt;span class="s2"&gt;"context="&lt;/span&gt; /etc/asterisk/sip-vicidial.conf | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue: Agents Not Receiving Calls Despite Being Available
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom&lt;/strong&gt;: Agents log in, show as READY, but calls don't route to them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check agent in-group membership&lt;/span&gt;
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
SELECT u.user_id, u.status, ia.ingroup_id, ia.closer_flag 
FROM vicidial_users u 
LEFT JOIN vicidial_ingroup_agents ia ON u.user_id = ia.user_id 
WHERE u.user_id='agent001';
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solutions&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;If &lt;code&gt;ingroup_id&lt;/code&gt; is NULL, agent not assigned to in-group. Re-run Step 3.1.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verify closer_enabled flag:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT user_id, closer_enabled FROM vicidial_users WHERE user_id='agent001';"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check if agent paused:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"show channels | grep agent001"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If showing paused status, use admin panel to unpause or force status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'READY'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'agent001'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check Asterisk queue membership:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"queue show CLOSER_SALES_MAIN | grep agent001"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue: High Call Abandonment Rate
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom&lt;/strong&gt;: Calls dropping before reaching agents (&amp;gt;15% abandonment).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Analysis&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
SELECT 
  ingroup_id,
  COUNT(*) as total,
  SUM(CASE WHEN call_status='ABANDON' THEN 1 ELSE 0 END) as abandoned,
  ROUND(100*SUM(CASE WHEN call_status='ABANDON' THEN 1 ELSE 0 END)/COUNT(*), 2) as abandon_pct,
  AVG(wait_time) as avg_wait_sec
FROM vicidial_closer_log
WHERE DATE(start_time) = CURDATE()
  AND campaign_id='CLOSER_SALES'
GROUP BY ingroup_id;
"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solutions&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Increase agents assigned&lt;/strong&gt;: Add more agents to in-group or create overflow in-group.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reduce timeout&lt;/strong&gt;: Lower &lt;code&gt;call_timeout&lt;/code&gt; value (but not below 60 seconds):&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroups&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;call_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'90'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add voicemail option&lt;/strong&gt;: Ensure &lt;code&gt;voicemail_ext&lt;/code&gt; is configured:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroups&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;voicemail_ext&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'9999'&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Implement callbacks&lt;/strong&gt;: Enable callback option when queue full.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Issue: Calls Queuing But Not Distributed
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom&lt;/strong&gt;: Queue shows calls waiting but agents not getting delivered calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Debug&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Monitor queue in real-time&lt;/span&gt;
watch &lt;span class="nt"&gt;-n&lt;/span&gt; 1 &lt;span class="s1"&gt;'asterisk -rx "queue show CLOSER_SALES_MAIN"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check agent member penalties:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT ingroup_id, user_id, agent_priority FROM vicidial_ingroup_agents WHERE ingroup_id='CLOSER_SALES_MAIN';"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Agent priority (penalty in queue) must be ascending (1, 2, 3, etc.). If reversed, queue won't distribute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroup_agents&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;agent_priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'agent001'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroup_agents&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;agent_priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'agent002'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"queue reload"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue: Calls Recording Not Working
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom&lt;/strong&gt;: Closer calls complete but no recordings saved.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check recording path&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lah&lt;/span&gt; /var/spool/asterisk/monitor/ | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Verify recording_enabled flag&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT ingroup_id, recording_enabled, recording_call_display_name FROM vicidial_ingroups WHERE ingroup_id='CLOSER_SALES_MAIN';"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Enable recordings&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroups&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;recording_enabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Y'&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES_MAIN'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Check Asterisk logging&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"monitor&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;record"&lt;/span&gt; /var/log/asterisk/messages | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Disk space issue&lt;/strong&gt; - common cause:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /var/spool/asterisk/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If full, archive old recordings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find /var/spool/asterisk/monitor/ &lt;span class="nt"&gt;-mtime&lt;/span&gt; +30 &lt;span class="nt"&gt;-exec&lt;/span&gt; &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue: Agent Can't Transfer Calls
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom&lt;/strong&gt;: Closer agent attempts blind/attended transfer but fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify transfer permissions&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT user_id, call_transfer_flag, can_transfer_to_other_users FROM vicidial_users WHERE user_id='agent001';"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Enable transfers&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; 
  &lt;span class="n"&gt;call_transfer_flag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;can_transfer_to_other_users&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'agent001'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Check Asterisk DTMF configuration&lt;/strong&gt; - verify feature codes in &lt;code&gt;/etc/asterisk/features.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[featuremap]
blindxfer =&amp;gt; #1          ; Blind transfer
atxfer =&amp;gt; *2             ; Attended transfer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Test with CLI&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core show hints | grep agent001"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Part 9: Monitoring &amp;amp; Reporting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dashboard Query: Real-Time In-Group Status
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;ig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingroup_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'READY'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;available_agents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'IN_CALL'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;busy_agents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'PAUSED'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;paused_agents&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt; 
   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingroup_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingroup_id&lt;/span&gt; 
   &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'WAITING'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;calls_waiting&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroups&lt;/span&gt; &lt;span class="n"&gt;ig&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_ingroup_agents&lt;/span&gt; &lt;span class="n"&gt;iag&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingroup_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingroup_id&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;iag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;ig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ingroup_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Daily Performance Report
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;call_status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'COMPLETED'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;completed_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;call_status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'ABANDON'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;abandoned_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_wait_seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_duration&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_talk_seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_handle_time_seconds&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_closer_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'CLOSER_SALES'&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;start_time&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CURDATE&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ingroup_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ingroup_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Agent Productivity Report
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;calls_handled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_talk_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_duration&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_talk_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_wait_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'COMPLETED'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_id&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;completed_calls&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_closer_log&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assigned_to&lt;/span&gt; 
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CURDATE&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;closer_enabled&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;full_name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;calls_handled&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Configuring ViciDial closer campaigns and in-groups involves seven critical steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create the closer campaign&lt;/strong&gt; in web UI with appropriate dialing and recording settings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Define in-groups&lt;/strong&gt; as call queues within the campaign, setting timeouts and priorities&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assign agents&lt;/strong&gt; to in-groups with individual priorities and closer permissions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable Asterisk integration&lt;/strong&gt; by verifying dialplan contexts and reloading configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test thoroughly&lt;/strong&gt; using Asterisk CLI and database verification queries&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize performance&lt;/strong&gt; through distribution strategy tuning and timeout adjustments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor continuously&lt;/strong&gt; with real-time queries and daily reports&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Key production-grade practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always verify database changes&lt;/strong&gt; with SELECT queries before and after modifications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use agent priorities&lt;/strong&gt; (1-3 range) for fair call distribution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable call recording&lt;/strong&gt; on all in-groups for compliance and quality assurance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set reasonable timeouts&lt;/strong&gt; (120-180 seconds) to prevent excessive call abandonment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create overflow in-groups&lt;/strong&gt; to handle peak traffic periods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test complete call flow&lt;/strong&gt; end-to-&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sip</category>
      <category>voip</category>
    </item>
    <item>
      <title>How to Use ViciDial Real-Time Report for Campaign Monitoring</title>
      <dc:creator>Moisi Trungu</dc:creator>
      <pubDate>Fri, 09 Oct 2026 03:30:04 +0000</pubDate>
      <link>https://dev.to/moisi_trungu_31647b7ac300/how-to-use-vicidial-real-time-report-for-campaign-monitoring-mac</link>
      <guid>https://dev.to/moisi_trungu_31647b7ac300/how-to-use-vicidial-real-time-report-for-campaign-monitoring-mac</guid>
      <description>&lt;h1&gt;
  
  
  How to Use ViciDial Real-Time Report for Campaign Monitoring
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Master real-time call monitoring, agent performance tracking, and live campaign metrics in ViciDial to optimize your contact center operations and identify bottlenecks instantly.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before implementing real-time report monitoring in ViciDial, ensure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A fully functional ViciDial installation (version 2.10 or later recommended)&lt;/li&gt;
&lt;li&gt;SSH/terminal access to your ViciDial server&lt;/li&gt;
&lt;li&gt;MySQL/MariaDB administrative access&lt;/li&gt;
&lt;li&gt;Understanding of basic ViciDial campaign structure (campaigns, user accounts, inbound/outbound queues)&lt;/li&gt;
&lt;li&gt;Web browser access to the ViciDial admin interface (&lt;code&gt;http://your-server/vicidial/admin.php&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Asterisk running with proper CDR logging enabled&lt;/li&gt;
&lt;li&gt;Write permissions to &lt;code&gt;/var/log/asterisk/&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;At least one active campaign configured with agents&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding ViciDial Real-Time Reporting Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How Real-Time Reports Work
&lt;/h3&gt;

&lt;p&gt;ViciDial real-time reports pull data directly from the Asterisk database (&lt;code&gt;asterisk&lt;/code&gt; schema) rather than relying on pre-calculated reports. The system queries active tables like &lt;code&gt;vicidial_log&lt;/code&gt;, &lt;code&gt;vicidial_closer_log&lt;/code&gt;, &lt;code&gt;vicidial_list&lt;/code&gt;, and &lt;code&gt;vicidial_users&lt;/code&gt; to display live metrics.&lt;/p&gt;

&lt;p&gt;The key difference between real-time reports and historical reports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time reports&lt;/strong&gt;: Query current call activity, agent status, and campaign state every few seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical reports&lt;/strong&gt;: Aggregate completed data from &lt;code&gt;vicidial_log&lt;/code&gt; after calls disconnect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Real-time reports use three primary data sources:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;vicidial_log&lt;/strong&gt; - Individual call records with timestamps, results, and agent assignments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;vicidial_closer_log&lt;/strong&gt; - Detailed call outcomes (sale, no-sale, callback, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;vicidial_users&lt;/strong&gt; - Agent status and live availability&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Database Table Structure
&lt;/h3&gt;

&lt;p&gt;Understanding the relevant tables ensures you query the right data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Check core tables for real-time data&lt;/span&gt;
&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;asterisk&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DESCRIBE&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DESCRIBE&lt;/span&gt; &lt;span class="n"&gt;vicidial_closer_log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DESCRIBE&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DESCRIBE&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key columns in &lt;code&gt;vicidial_log&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;call_id&lt;/code&gt; - Unique call identifier&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;campaign_id&lt;/code&gt; - Campaign this call belongs to&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user&lt;/code&gt; - Agent username&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;call_date&lt;/code&gt; - Call timestamp&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;call_type&lt;/code&gt; - INBOUND, OUTBOUND, etc.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;call_result&lt;/code&gt; - Final disposition&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;call_length&lt;/code&gt; - Duration in seconds&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt; - Current state (LIVE, COMPLETED, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Accessing the Real-Time Report Interface
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Web-Based Real-Time Reports
&lt;/h3&gt;

&lt;p&gt;The primary method to view real-time reports is through the ViciDial admin interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://your-vicidial-server/vicidial/admin.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Steps to access real-time reports:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Log in with your administrative account&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Reports&lt;/strong&gt; → &lt;strong&gt;Real-Time Reports&lt;/strong&gt; (or &lt;strong&gt;Live Calls&lt;/strong&gt; depending on version)&lt;/li&gt;
&lt;li&gt;Select your campaign from the dropdown&lt;/li&gt;
&lt;li&gt;The dashboard automatically refreshes every 5-10 seconds&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Command-Line Real-Time Queries
&lt;/h3&gt;

&lt;p&gt;For server-side monitoring or custom integrations, query the database directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# SSH into your ViciDial server&lt;/span&gt;
ssh root@your-vicidial-server

&lt;span class="c"&gt;# Connect to MySQL&lt;/span&gt;
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt;

&lt;span class="c"&gt;# Select the asterisk database&lt;/span&gt;
USE asterisk&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical Real-Time Monitoring Queries
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Query 1: Active Calls by Campaign (Last 5 Minutes)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;active_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TIMESTAMPDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SECOND&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_call_duration_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SALE'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SALE'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;conversion_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;
    &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; 
    &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;MINUTE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'LIVE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'COMPLETED'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_name&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="n"&gt;active_calls&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Output example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------+--------------+------------------------+-------+-----------------+
| campaign_name         | active_calls | avg_call_duration_sec  | sales | conversion_rate |
+-----------------------+--------------+------------------------+-------+-----------------+
| Sales Campaign Q4     |           23 |                    87  |     5 |            21.74|
| Lead Qualification    |           18 |                   124  |     2 |            11.11|
| Customer Retention    |            7 |                    45  |     1 |            14.29|
+-----------------------+--------------+------------------------+-------+-----------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query 2: Agent Performance Real-Time Snapshot
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;calls_today&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_length&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_call_length_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SALE'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'SALE'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;sales_rate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;last_call_time&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;vicidial_users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
    &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt; 
        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; 
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_level&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_level&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;calls_today&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query 3: Queue Depth and Wait Times
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dial_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;current_queue_depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TIMESTAMPDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SECOND&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;())),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_queue_wait_sec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;available_agents&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;
    &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; 
    &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'LIVE'&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="k"&gt;MINUTE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dial_method&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query 4: Real-Time Call Result Distribution
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;result_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;percentage&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;
    &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; 
    &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_result&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="n"&gt;result_count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building Custom Real-Time Monitoring Dashboards
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Creating a Shell Script for Continuous Monitoring
&lt;/h3&gt;

&lt;p&gt;Create a bash script that polls real-time data and displays it in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# File: /usr/local/bin/vicidial-monitor.sh&lt;/span&gt;
&lt;span class="c"&gt;# Real-time ViciDial monitoring script&lt;/span&gt;

&lt;span class="nv"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;
&lt;span class="nv"&gt;MYSQL_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_mysql_password"&lt;/span&gt;
&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"asterisk"&lt;/span&gt;
&lt;span class="nv"&gt;REFRESH_INTERVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10

&lt;span class="k"&gt;function &lt;/span&gt;get_active_calls&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    mysql &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nv"&gt;$MYSQL_USER&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="nv"&gt;$MYSQL_PASS&lt;/span&gt; &lt;span class="nv"&gt;$DB_NAME&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
    SELECT 
        c.campaign_name,
        COUNT(*) as active_calls,
        SUM(CASE WHEN vl.call_result = 'SALE' THEN 1 ELSE 0 END) as sales
    FROM 
        vicidial_log vl
        INNER JOIN vicidial_campaigns c ON vl.campaign_id = c.campaign_id
    WHERE 
        vl.call_date &amp;gt;= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
        AND vl.status IN ('LIVE', 'COMPLETED')
    GROUP BY 
        c.campaign_name
    ORDER BY 
        active_calls DESC;
    "&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function &lt;/span&gt;get_agent_status&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    mysql &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nv"&gt;$MYSQL_USER&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="nv"&gt;$MYSQL_PASS&lt;/span&gt; &lt;span class="nv"&gt;$DB_NAME&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
    SELECT 
        user,
        status,
        COUNT(*) as count
    FROM 
        vicidial_users
    WHERE 
        user_level &amp;gt; 0
    GROUP BY 
        user, status;
    "&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Main loop&lt;/span&gt;
&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;clear
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"====== VICIDIAL REAL-TIME MONITOR ======"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Refresh Time: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y-%m-%d %H:%M:%S'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--- ACTIVE CALLS BY CAMPAIGN ---"&lt;/span&gt;
    get_active_calls
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--- AGENT STATUS SUMMARY ---"&lt;/span&gt;
    get_agent_status
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Next refresh in &lt;/span&gt;&lt;span class="nv"&gt;$REFRESH_INTERVAL&lt;/span&gt;&lt;span class="s2"&gt; seconds..."&lt;/span&gt;
    &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="nv"&gt;$REFRESH_INTERVAL&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Make it executable:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/vicidial-monitor.sh

&lt;span class="c"&gt;# Run it&lt;/span&gt;
/usr/local/bin/vicidial-monitor.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  PHP-Based Real-Time Dashboard
&lt;/h3&gt;

&lt;p&gt;Create a lightweight PHP dashboard for web access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// File: /var/www/html/vicidial/real-time-dashboard.php&lt;/span&gt;
&lt;span class="c1"&gt;// Simple real-time dashboard&lt;/span&gt;

&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Content-Type: text/html; charset=utf-8'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$db_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$db_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'root'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$db_pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$db_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'asterisk'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;mysqli&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$db_host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$db_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$db_pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$db_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$conn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;connect_error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Database connection failed: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$conn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;connect_error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Query active calls&lt;/span&gt;
&lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"
SELECT 
    c.campaign_name,
    COUNT(*) as active_calls,
    SUM(CASE WHEN vl.call_result = 'SALE' THEN 1 ELSE 0 END) as sales,
    AVG(vl.call_length) as avg_duration
FROM 
    vicidial_log vl
    INNER JOIN vicidial_campaigns c ON vl.campaign_id = c.campaign_id
WHERE 
    vl.call_date &amp;gt;= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
    AND vl.status IN ('LIVE', 'COMPLETED')
GROUP BY 
    c.campaign_name
ORDER BY 
    active_calls DESC
"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$conn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;ViciDial Real-Time Monitor&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"refresh"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"10"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Arial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f5f5f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nt"&gt;table&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;border-collapse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;collapse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;td&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;border-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#ddd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nt"&gt;th&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#4CAF50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f5f5f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.metric&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2196F3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.sales&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#4CAF50&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nc"&gt;.timestamp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#999&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.9em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;ViciDial Real-Time Report - Campaign Monitor&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"timestamp"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Last Updated: &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt; &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Y-m-d H:i:s'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;table&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Campaign Name&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"metric"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Active Calls&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;th&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"sales"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Sales&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Avg Duration (sec)&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;num_rows&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;fetch_assoc&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;tr&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;td&amp;gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;htmlspecialchars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'campaign_name'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;/td&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;td class='metric'&amp;gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'active_calls'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;/td&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;td class='sales'&amp;gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'sales'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;/td&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;td&amp;gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'avg_duration'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;/td&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;/tr&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;tr&amp;gt;&amp;lt;td colspan='4'&amp;gt;No active calls&amp;lt;/td&amp;gt;&amp;lt;/tr&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nv"&gt;$conn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Access it at: &lt;code&gt;http://your-server/vicidial/real-time-dashboard.php&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring Specific Metrics for Campaign Health
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Call Abandonment Rate
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ABANDON'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;abandoned&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ABANDON'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;abandon_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;
    &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; 
    &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'INBOUND'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_name&lt;/span&gt;
&lt;span class="k"&gt;HAVING&lt;/span&gt; 
    &lt;span class="n"&gt;abandon_rate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="n"&gt;abandon_rate&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Average Handle Time (AHT) by Agent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_length&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;aht_seconds&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;aht_minutes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_talk_hours&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;
    &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; 
    &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'OUTBOUND'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="n"&gt;aht_seconds&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dial-Through Rate (Predictive/Progressive Dialing)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dial_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_dials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_result&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NO ANSWER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'BUSY'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_result&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'NO ANSWER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'BUSY'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;connect_rate&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;
    &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; 
    &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'OUTBOUND'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dial_method&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring Alerts for Campaign Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setting Up Threshold-Based Alerts
&lt;/h3&gt;

&lt;p&gt;Create a monitoring script that sends alerts when metrics exceed thresholds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# File: /usr/local/bin/vicidial-alert-monitor.sh&lt;/span&gt;
&lt;span class="c"&gt;# Alert when metrics exceed thresholds&lt;/span&gt;

&lt;span class="nv"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"root"&lt;/span&gt;
&lt;span class="nv"&gt;MYSQL_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"password"&lt;/span&gt;
&lt;span class="nv"&gt;DB_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"asterisk"&lt;/span&gt;
&lt;span class="nv"&gt;EMAIL_TO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"admin@company.com"&lt;/span&gt;

&lt;span class="c"&gt;# Check abandon rate&lt;/span&gt;
&lt;span class="nv"&gt;ABANDON_RATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nv"&gt;$MYSQL_USER&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="nv"&gt;$MYSQL_PASS&lt;/span&gt; &lt;span class="nv"&gt;$DB_NAME&lt;/span&gt; &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
SELECT MAX(abandon_pct) FROM (
    SELECT 
        ROUND(100.0 * SUM(CASE WHEN call_result = 'ABANDON' THEN 1 ELSE 0 END) / COUNT(*), 2) as abandon_pct
    FROM 
        vicidial_log
    WHERE 
        call_date &amp;gt;= DATE_SUB(NOW(), INTERVAL 1 HOUR)
        AND call_type = 'INBOUND'
    GROUP BY campaign_id
) subquery;
"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;ABANDON_THRESHOLD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;15

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ABANDON_RATE&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$ABANDON_THRESHOLD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | bc &lt;span class="nt"&gt;-l&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ALERT: Abandon rate is &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ABANDON_RATE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% (threshold: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ABANDON_THRESHOLD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%)"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
    mail &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"ViciDial Alert: High Abandon Rate"&lt;/span&gt; &lt;span class="nv"&gt;$EMAIL_TO&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Check active agent availability&lt;/span&gt;
&lt;span class="nv"&gt;AVAILABLE_AGENTS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nv"&gt;$MYSQL_USER&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="nv"&gt;$MYSQL_PASS&lt;/span&gt; &lt;span class="nv"&gt;$DB_NAME&lt;/span&gt; &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
SELECT COUNT(*) FROM vicidial_users 
WHERE user_level &amp;gt; 0 
AND status IN ('READY', 'PAUSED')
AND active = 'Y'
"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;QUEUE_DEPTH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nv"&gt;$MYSQL_USER&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="nv"&gt;$MYSQL_PASS&lt;/span&gt; &lt;span class="nv"&gt;$DB_NAME&lt;/span&gt; &lt;span class="nt"&gt;-N&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"
SELECT COUNT(*) FROM vicidial_log 
WHERE status = 'LIVE' 
AND call_date &amp;gt;= DATE_SUB(NOW(), INTERVAL 5 MINUTE)
"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AVAILABLE_AGENTS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-lt&lt;/span&gt; 5 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$QUEUE_DEPTH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-gt&lt;/span&gt; 20 &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ALERT: Low agent availability (&lt;/span&gt;&lt;span class="nv"&gt;$AVAILABLE_AGENTS&lt;/span&gt;&lt;span class="s2"&gt; agents) with queue depth at &lt;/span&gt;&lt;span class="nv"&gt;$QUEUE_DEPTH&lt;/span&gt;&lt;span class="s2"&gt; calls"&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
    mail &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"ViciDial Alert: Understaffing Risk"&lt;/span&gt; &lt;span class="nv"&gt;$EMAIL_TO&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Add to crontab to run every minute:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crontab &lt;span class="nt"&gt;-e&lt;/span&gt;

&lt;span class="c"&gt;# Add this line:&lt;/span&gt;
&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; /usr/local/bin/vicidial-alert-monitor.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Optimization for Real-Time Reports
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Database Indexing for Faster Queries
&lt;/h3&gt;

&lt;p&gt;Large ViciDial installations can experience slow real-time reports without proper indexing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Add indexes if they don't exist&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_call_date&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_campaign_status&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_user_date&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;vicidial_closer_log&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_call_date&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Verify indexes exist&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Limiting Query Scope
&lt;/h3&gt;

&lt;p&gt;Never query unbounded date ranges in production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- GOOD: Limited to last hour&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;-- BAD: No date filter - scans entire table&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'john.smith'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using READ REPLICAS for Reporting
&lt;/h3&gt;

&lt;p&gt;For high-volume operations, query a read replica instead of the production database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example: Redirect reporting queries to replica&lt;/span&gt;
&lt;span class="nv"&gt;REPLICA_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"vicidial-replica.internal"&lt;/span&gt;
&lt;span class="nv"&gt;REPLICA_USER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"report_user"&lt;/span&gt;
&lt;span class="nv"&gt;REPLICA_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"replica_password"&lt;/span&gt;

mysql &lt;span class="nt"&gt;-h&lt;/span&gt; &lt;span class="nv"&gt;$REPLICA_HOST&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nv"&gt;$REPLICA_USER&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;&lt;span class="nv"&gt;$REPLICA_PASS&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT ..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Integrating with Asterisk CLI for Live Monitoring
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Real-Time Agent Status from Asterisk
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check SIP channels&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip show channels"&lt;/span&gt;

&lt;span class="c"&gt;# Check Asterisk queue status&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"queue show"&lt;/span&gt;

&lt;span class="c"&gt;# Get specific queue info&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"queue show vicidial_queue"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sample output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vicidial_queue has 3 calls (max unlimited) in 'ringall' strategy (0s holdtime, 45s talktime), W:0, C:125, A:3, SL:98.4% within 45s
    SIP/2002 (Agent John) with 1 calls (dynamic) answered in 124s
    SIP/2003 (Agent Sarah) with 1 calls (dynamic) answered in 156s
    Local/3051@from-internal-00000001 (IVR Menu) with 1 calls (dynamic) answered in 89s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pull CDR Data from Asterisk
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check Asterisk call records&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"cdr show active"&lt;/span&gt;

&lt;span class="c"&gt;# Get CDR SQL status&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"cdr show status"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting Common Real-Time Reporting Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Issue 1: Real-Time Reports Show Zero Calls
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Dashboard displays "No active calls" when calls are clearly being processed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify vicidial_log table is being populated:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;recent_records&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;MINUTE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check if campaigns are properly configured:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; 
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;MINUTE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Verify Asterisk is logging to MySQL:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check Asterisk CDR module&lt;/span&gt;
asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"cdr show status"&lt;/span&gt;

&lt;span class="c"&gt;# Expected output: CDR logging enabled, MySQL module loaded&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Restart CDR logging if needed:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"cdr reload"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 2: Real-Time Reports Run Slowly
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Dashboard takes 30+ seconds to load; queries timeout.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Analyze slow query log:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Enable slow query log&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;GLOBAL&lt;/span&gt; &lt;span class="n"&gt;slow_query_log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ON'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;GLOBAL&lt;/span&gt; &lt;span class="n"&gt;long_query_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- View slow queries&lt;/span&gt;
&lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;slow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check query execution plan:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;EXPLAIN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;your&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="n"&gt;here&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;-- Look for "Full table scan" entries - these need indexes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Clear old data to reduce table size:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Archive calls older than 90 days&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;vicidial_log_archive_2024&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_log_archive_2024&lt;/span&gt; 
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Optimize table&lt;/span&gt;
&lt;span class="n"&gt;OPTIMIZE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 3: Refresh Lag or Stale Data
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Real-time dashboard shows outdated information; metrics don't refresh.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check database replication status (if using replication):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;SLAVE&lt;/span&gt; &lt;span class="n"&gt;STATUS&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;G&lt;/span&gt;

&lt;span class="c1"&gt;-- Look for Seconds_Behind_Master value&lt;/span&gt;
&lt;span class="c1"&gt;-- Should be 0 or very small&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Verify web server isn't caching the report:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Add to real-time dashboard header&lt;/span&gt;
&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Cache-Control: no-store, no-cache, must-revalidate, max-age=0"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Pragma: no-cache"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Expires: 0"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Increase refresh frequency in dashboard:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Change refresh interval from 10s to 5s --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;http-equiv=&lt;/span&gt;&lt;span class="s"&gt;"refresh"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 4: Database Locks During Real-Time Queries
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Queries hang; "Lock wait timeout exceeded" errors in MySQL log.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify locked tables:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="k"&gt;OPEN&lt;/span&gt; &lt;span class="n"&gt;TABLES&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;In_use&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Check for long-running transactions&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;PROCESSLIST&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Kill blocking query if necessary:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;KILL&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PROCESS_ID&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Configure MySQL to use InnoDB (better for concurrent access):
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Edit /etc/mysql/my.cnf&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;mysqld]
default-storage-engine&lt;span class="o"&gt;=&lt;/span&gt;InnoDB
&lt;span class="nv"&gt;innodb_buffer_pool_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4G
&lt;span class="nv"&gt;innodb_log_file_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;512M
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 5: PHP Dashboard Blank or Shows Database Errors
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Real-time dashboard page is blank or displays "Database connection failed."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check PHP error logs:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/apache2/error.log
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/php-fpm.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Verify MySQL credentials in dashboard script:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;mysqli&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'root'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'password'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'asterisk'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$conn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;connect_error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Error: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$conn&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;connect_error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Test MySQL connectivity from command line:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; localhost asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT COUNT(*) FROM vicidial_log LIMIT 1;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check database user permissions:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Verify user has SELECT permission&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;GRANTS&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="s1"&gt;'root'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Grant if needed&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;asterisk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="s1"&gt;'root'&lt;/span&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;FLUSH&lt;/span&gt; &lt;span class="k"&gt;PRIVILEGES&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;ViciDial real-time reporting provides critical insight into campaign performance, agent activity, and system health. Key takeaways:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Concepts:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Real-time reports query live data from &lt;code&gt;vicidial_log&lt;/code&gt;, &lt;code&gt;vicidial_users&lt;/code&gt;, and &lt;code&gt;vicidial_campaigns&lt;/code&gt; tables&lt;/li&gt;
&lt;li&gt;Data refreshes every 5-10 seconds in web interface; can be queried instantly via CLI&lt;/li&gt;
&lt;li&gt;Direct MySQL queries offer flexibility beyond built-in reports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Practical Implementation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use web-based dashboards at &lt;code&gt;/vicidial/admin.php&lt;/code&gt; for quick overview&lt;/li&gt;
&lt;li&gt;Build custom bash scripts for server-side monitoring&lt;/li&gt;
&lt;li&gt;Create PHP dashboards for team visibility&lt;/li&gt;
&lt;li&gt;Implement threshold-based alerts for critical metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key Metrics to Monitor:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active calls by campaign&lt;/li&gt;
&lt;li&gt;Agent performance (AHT, sales rate, availability)&lt;/li&gt;
&lt;li&gt;Queue depth and abandon rates&lt;/li&gt;
&lt;li&gt;Dial-through rates and connection quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Performance Best Practices:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add database indexes on &lt;code&gt;call_date&lt;/code&gt;, &lt;code&gt;campaign_id&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, and &lt;code&gt;user&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Limit queries to recent time windows (last 1-24 hours)&lt;/li&gt;
&lt;li&gt;Archive old data to keep tables responsive&lt;/li&gt;
&lt;li&gt;Use read replicas for reporting in high-volume environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Troubleshooting Foundation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify MySQL logging is active and connected to Asterisk&lt;/li&gt;
&lt;li&gt;Check slow query logs and optimize with indexes&lt;/li&gt;
&lt;li&gt;Clear caches in PHP headers to ensure fresh data&lt;/li&gt;
&lt;li&gt;Monitor database locks and process list during issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With proper real-time monitoring in place, you'll detect abandoned calls, staffing shortages, and performance bottlenecks within minutes rather than hours—enabling proactive response that directly impacts customer satisfaction and campaign ROI.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sip</category>
      <category>voip</category>
    </item>
    <item>
      <title>How to Configure ViciDial Script Tab — Agent Call Scripts</title>
      <dc:creator>Moisi Trungu</dc:creator>
      <pubDate>Thu, 08 Oct 2026 03:30:04 +0000</pubDate>
      <link>https://dev.to/moisi_trungu_31647b7ac300/how-to-configure-vicidial-script-tab-agent-call-scripts-cjl</link>
      <guid>https://dev.to/moisi_trungu_31647b7ac300/how-to-configure-vicidial-script-tab-agent-call-scripts-cjl</guid>
      <description>&lt;h1&gt;
  
  
  How to Configure ViciDial Script Tab — Agent Call Scripts
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Master the complete setup of ViciDial agent call scripts including database configuration, script creation, script-to-campaign linking, and agent-level script assignments for production call centers.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before configuring ViciDial agent call scripts, ensure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A running ViciDial installation (version 2.9+ recommended)&lt;/li&gt;
&lt;li&gt;Root or sudo access to the ViciDial server&lt;/li&gt;
&lt;li&gt;MySQL/MariaDB access with credentials to the &lt;code&gt;asterisk&lt;/code&gt; database&lt;/li&gt;
&lt;li&gt;Understanding of ViciDial user roles (Admin, Manager, Agent)&lt;/li&gt;
&lt;li&gt;A text editor (nano, vim, or IDE)&lt;/li&gt;
&lt;li&gt;Access to the ViciDial web administration interface at &lt;code&gt;/vicidial/admin.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;At least one active campaign already created in the system&lt;/li&gt;
&lt;li&gt;Basic knowledge of HTML/plain text formatting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding ViciDial Script Architecture
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Are Call Scripts?
&lt;/h3&gt;

&lt;p&gt;Call scripts in ViciDial are configurable templates that agents see during active calls. They serve multiple purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visual guidance&lt;/strong&gt; during customer interactions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic variable substitution&lt;/strong&gt; pulling from lead records&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Call flow logic&lt;/strong&gt; with conditional branching&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration with disposition codes&lt;/strong&gt; and call outcomes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Customizable HTML/plain text rendering&lt;/strong&gt; in the agent screen&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Script Processing Flow
&lt;/h3&gt;

&lt;p&gt;When an agent receives a call, ViciDial:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retrieves the assigned script from the database&lt;/li&gt;
&lt;li&gt;Parses special syntax tokens (e.g., &lt;code&gt;[FIRSTNAME]&lt;/code&gt;, &lt;code&gt;[PHONE]&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Substitutes lead-specific variables from &lt;code&gt;vicidial_list&lt;/code&gt; table&lt;/li&gt;
&lt;li&gt;Renders the script in the agent's browser in real-time&lt;/li&gt;
&lt;li&gt;Logs script interactions if configured&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Database Structure for Scripts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Core Script Tables
&lt;/h3&gt;

&lt;p&gt;ViciDial stores scripts in the &lt;code&gt;asterisk&lt;/code&gt; database across several key tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- View existing script table structure&lt;/span&gt;
&lt;span class="k"&gt;DESCRIBE&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key columns in &lt;code&gt;vicidial_scripts&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;script_id&lt;/td&gt;
&lt;td&gt;INT (Primary Key)&lt;/td&gt;
&lt;td&gt;Unique identifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;script_name&lt;/td&gt;
&lt;td&gt;VARCHAR(255)&lt;/td&gt;
&lt;td&gt;Display name for admin panel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;script_text&lt;/td&gt;
&lt;td&gt;LONGTEXT&lt;/td&gt;
&lt;td&gt;Script content with variables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;script_lang&lt;/td&gt;
&lt;td&gt;VARCHAR(10)&lt;/td&gt;
&lt;td&gt;Language code (default: en)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;active&lt;/td&gt;
&lt;td&gt;ENUM('Y','N')&lt;/td&gt;
&lt;td&gt;Enable/disable script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;user_group&lt;/td&gt;
&lt;td&gt;VARCHAR(20)&lt;/td&gt;
&lt;td&gt;Restrict to specific user groups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;date_added&lt;/td&gt;
&lt;td&gt;TIMESTAMP&lt;/td&gt;
&lt;td&gt;Creation timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;date_modified&lt;/td&gt;
&lt;td&gt;TIMESTAMP&lt;/td&gt;
&lt;td&gt;Last modification timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Script-Campaign Linking Table
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;vicidial_campaigns&lt;/code&gt; table contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Relevant columns for script assignment&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_screen&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'TESTCAMP'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;script_id&lt;/code&gt; — Links to &lt;code&gt;vicidial_scripts.script_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;script_screen&lt;/code&gt; — Controls when script displays ('CALL_TIME', 'DIALER_PREVIEW', 'BOTH', 'NONE')&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Creating Your First Agent Call Script
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Connect to the ViciDial Database
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; cron &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When prompted, enter your ViciDial database password (typically found in &lt;code&gt;/etc/astguiclient.conf&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'ASTERISK_PASSWD'&lt;/span&gt; /etc/astguiclient.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Insert a Basic Script
&lt;/h3&gt;

&lt;p&gt;Create a simple inbound support script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; 
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_added&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'Inbound Support - Basic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Hello [FIRSTNAME], thank you for calling. What can I help you with today?

Account Info:
Customer: [FIRSTNAME] [LASTNAME]
Account: [ACCOUNTID]
Phone: [PHONE]
Email: [EMAIL]

[NOTES]

Type your notes above this line.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'AGENTS'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify insertion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;script_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Inbound Support - Basic'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Create an Advanced Script with HTML Formatting
&lt;/h3&gt;

&lt;p&gt;For more sophisticated formatting in the agent screen, use HTML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; 
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'Outbound Sales - Advanced'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'&amp;lt;div style="background-color: #f0f0f0; padding: 10px; font-family: Arial;"&amp;gt;
    &amp;lt;h3&amp;gt;Sales Call Script&amp;lt;/h3&amp;gt;

    &amp;lt;fieldset&amp;gt;
      &amp;lt;legend&amp;gt;Customer Information&amp;lt;/legend&amp;gt;
      &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Name:&amp;lt;/strong&amp;gt; [FIRSTNAME] [LASTNAME]&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Phone:&amp;lt;/strong&amp;gt; [PHONE]&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;Last Contact:&amp;lt;/strong&amp;gt; [LASTCALLDATE]&amp;lt;/p&amp;gt;
    &amp;lt;/fieldset&amp;gt;

    &amp;lt;fieldset&amp;gt;
      &amp;lt;legend&amp;gt;Opening Statement&amp;lt;/legend&amp;gt;
      &amp;lt;p&amp;gt;Hi [FIRSTNAME], this is [AGENTNAME] from TechCorp Solutions. 
      I&lt;/span&gt;&lt;span class="se"&gt;''&lt;/span&gt;&lt;span class="s1"&gt;m calling because we have a special offer for valued customers like you.&amp;lt;/p&amp;gt;
    &amp;lt;/fieldset&amp;gt;

    &amp;lt;fieldset&amp;gt;
      &amp;lt;legend&amp;gt;Discovery Questions&amp;lt;/legend&amp;gt;
      &amp;lt;ol&amp;gt;
        &amp;lt;li&amp;gt;Are you currently using a solution for [LEADTYPE]?&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;What is your biggest pain point?&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;When would be a good time to discuss options?&amp;lt;/li&amp;gt;
      &amp;lt;/ol&amp;gt;
    &amp;lt;/fieldset&amp;gt;

    &amp;lt;fieldset style="background-color: #fff3cd; border: 1px solid #ffc107;"&amp;gt;
      &amp;lt;legend&amp;gt;Agent Notes&amp;lt;/legend&amp;gt;
      &amp;lt;textarea style="width: 100%; height: 80px;"&amp;gt;
[NOTES]
      &amp;lt;/textarea&amp;gt;
    &amp;lt;/fieldset&amp;gt;
  &amp;lt;/div&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'AGENTS'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ViciDial Script Variables Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Available Token Variables
&lt;/h3&gt;

&lt;p&gt;These tokens are automatically replaced with lead data during call display:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[FIRSTNAME]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vicidial_list.first_name&lt;/td&gt;
&lt;td&gt;"John"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[LASTNAME]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vicidial_list.last_name&lt;/td&gt;
&lt;td&gt;"Smith"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[PHONE]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vicidial_list.phone_number&lt;/td&gt;
&lt;td&gt;"555-123-4567"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[EMAIL]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vicidial_list.email&lt;/td&gt;
&lt;td&gt;"&lt;a href="mailto:john@example.com"&gt;john@example.com&lt;/a&gt;"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[NOTES]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vicidial_list.notes&lt;/td&gt;
&lt;td&gt;User-editable field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[LEADTYPE]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vicidial_list.lead_source&lt;/td&gt;
&lt;td&gt;"Web Form"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[ACCOUNTID]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vicidial_list.record_id&lt;/td&gt;
&lt;td&gt;"ACC-12345"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[AGENTNAME]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vicidial_users.full_name&lt;/td&gt;
&lt;td&gt;"Sarah Johnson"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[LASTCALLDATE]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vicidial_log.call_date&lt;/td&gt;
&lt;td&gt;"2024-01-15 14:32"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;[LASTDISPO]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;vicidial_closer_log.status&lt;/td&gt;
&lt;td&gt;"CALLBACK"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;[CUSTOM1]&lt;/code&gt; through &lt;code&gt;[CUSTOM10]&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;vicidial_list.alt_phone, field1-field10&lt;/td&gt;
&lt;td&gt;Custom lead data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Conditional Variables (Advanced)
&lt;/h3&gt;

&lt;p&gt;For conditional display based on lead data, use PHP-style logic in custom integrations. Standard ViciDial scripts use simple token replacement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linking Scripts to Campaigns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Via MySQL
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Assign script to campaign&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; 
                 &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;script_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Outbound Sales - Advanced'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;script_screen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CALL_TIME'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'TESTCAMP'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Verify assignment&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'TESTCAMP'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Via Web Admin Interface
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log into &lt;code&gt;/vicidial/admin.php&lt;/code&gt; as admin user&lt;/li&gt;
&lt;li&gt;Navigate: &lt;strong&gt;Campaigns&lt;/strong&gt; → Click campaign name&lt;/li&gt;
&lt;li&gt;Scroll to &lt;strong&gt;Script&lt;/strong&gt; section&lt;/li&gt;
&lt;li&gt;Select script from dropdown: &lt;em&gt;Outbound Sales - Advanced&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;Script Screen&lt;/strong&gt; option:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CALL_TIME&lt;/code&gt; — Show during active call only&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DIALER_PREVIEW&lt;/code&gt; — Show in preview mode before dialing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BOTH&lt;/code&gt; — Show in both modes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;NONE&lt;/code&gt; — Disable script display&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;SUBMIT&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Agent-Level Script Overrides
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Override Script Per User
&lt;/h3&gt;

&lt;p&gt;Some ViciDial configurations allow per-user script overrides. Store these in the &lt;code&gt;vicidial_users&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Check if user table has script_id column&lt;/span&gt;
&lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="n"&gt;COLUMNS&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'script%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- If column exists, assign script to specific user&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;script_override&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; 
                       &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;script_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Inbound Support - Basic'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'agent1'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the column doesn't exist, this functionality may not be enabled in your version. Typically, scripts are assigned at the campaign level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Script Management Operations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Backup Scripts
&lt;/h3&gt;

&lt;p&gt;Export all scripts before modifications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysqldump &lt;span class="nt"&gt;-u&lt;/span&gt; cron &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk vicidial_scripts &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /backup/vicidial_scripts_&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restore if needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; cron &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &amp;lt; /backup/vicidial_scripts_20240115.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  List All Active Scripts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_modified&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt; 
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;script_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bulk Update Scripts
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Deactivate all old scripts&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'N'&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;date_modified&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;script_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%OLD%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Check impact before committing&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;date_modified&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;script_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'%OLD%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Clone/Duplicate a Script
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create new version of existing script&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;' - v2'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
  &lt;span class="n"&gt;script_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;script_lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="n"&gt;user_group&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Script Display Settings in Asterisk Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Extension Configuration
&lt;/h3&gt;

&lt;p&gt;ViciDial's Asterisk dial plan reads script display settings from extensions configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;; /&lt;span class="n"&gt;etc&lt;/span&gt;/&lt;span class="n"&gt;asterisk&lt;/span&gt;/&lt;span class="n"&gt;extensions&lt;/span&gt;-&lt;span class="n"&gt;vicidial&lt;/span&gt;.&lt;span class="n"&gt;conf&lt;/span&gt; &lt;span class="n"&gt;excerpt&lt;/span&gt;
; &lt;span class="n"&gt;Script&lt;/span&gt; &lt;span class="n"&gt;display&lt;/span&gt; &lt;span class="n"&gt;controlled&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;campaign&lt;/span&gt; &lt;span class="n"&gt;configuration&lt;/span&gt;, 
; &lt;span class="n"&gt;but&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;overridden&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dial&lt;/span&gt; &lt;span class="n"&gt;plan&lt;/span&gt; &lt;span class="n"&gt;logic&lt;/span&gt;

&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;AGI&lt;/span&gt;(&lt;span class="n"&gt;agi&lt;/span&gt;://&lt;span class="m"&gt;127&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;0&lt;/span&gt;.&lt;span class="m"&gt;1&lt;/span&gt;:&lt;span class="m"&gt;4573&lt;/span&gt;/?&lt;span class="n"&gt;stagePROVIDED&lt;/span&gt;|${&lt;span class="n"&gt;EXTEN&lt;/span&gt;})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;9&lt;/span&gt;&lt;span class="n"&gt;X&lt;/span&gt;.,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Hangup&lt;/span&gt;()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AGI handler communicates script assignments to the web interface via WebSocket or AJAX polling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Scripts in Production
&lt;/h2&gt;

&lt;h3&gt;
  
  
  View Script as Agent Would See It
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log into &lt;code&gt;/agc/vicidial.php&lt;/code&gt; as test agent&lt;/li&gt;
&lt;li&gt;Place a test call to campaign with script assigned&lt;/li&gt;
&lt;li&gt;Script should display in right panel during call&lt;/li&gt;
&lt;li&gt;Verify variable substitution (names, phone numbers appear correctly)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Check Script Logs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/asterisk/messages | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; script
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test Variable Substitution
&lt;/h3&gt;

&lt;p&gt;Create a test lead record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; 
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_added&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'TESTCAMP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'5551234567'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'TestAgent'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Script'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'test@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'This is a test lead for script validation'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'NEW'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call the number and verify all tokens show correctly in the script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Script Size Optimization
&lt;/h3&gt;

&lt;p&gt;Large scripts (&amp;gt;50 KB) may slow browser rendering. Optimize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Check script sizes&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;size_bytes&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; 
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;size_bytes&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If scripts exceed 50 KB:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Move verbose instructions to external documentation&lt;/li&gt;
&lt;li&gt;Use collapsed HTML sections with toggle buttons&lt;/li&gt;
&lt;li&gt;Limit CSS inline styling (use class references instead)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Database Query Efficiency
&lt;/h3&gt;

&lt;p&gt;ViciDial caches script data. To force refresh:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Restart ViciDial daemon to clear cache&lt;/span&gt;
systemctl restart vicidial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or clear specific cache entries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# If Redis is enabled&lt;/span&gt;
redis-cli FLUSHDB  &lt;span class="c"&gt;# WARNING: Clears all cache&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Integration with Disposition Codes
&lt;/h2&gt;

&lt;p&gt;Scripts often reference disposition codes used after calls. Link them logically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- View available dispositions&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;disposition_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;disposition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_statuses&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'TESTCAMP'&lt;/span&gt; 
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create script sections matching dispositions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; 
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'Inbound Routing'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'&amp;lt;h2&amp;gt;Call Disposition Guide&amp;lt;/h2&amp;gt;

  &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;SALE:&amp;lt;/strong&amp;gt; Customer agreed to purchase&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;CALLBACK:&amp;lt;/strong&amp;gt; Customer wants follow-up call&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;NOTINTERESTED:&amp;lt;/strong&amp;gt; Customer declined offer&amp;lt;/p&amp;gt;
  &amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;DONOTCALL:&amp;lt;/strong&amp;gt; Customer requested removal from list&amp;lt;/p&amp;gt;

  &amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Select appropriate disposition after call ends.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'AGENTS'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Multi-Language Script Support
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create Scripts in Multiple Languages
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- English version&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Welcome Script'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Hello [FIRSTNAME], welcome!'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'en'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Spanish version&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Welcome Script'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'¡Hola [FIRSTNAME], bienvenido!'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'es'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- French version&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;script_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_lang&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Welcome Script'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Bonjour [FIRSTNAME], bienvenue!'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fr'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The campaign or user language setting determines which version displays.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advanced: Custom Script Variables via AGI
&lt;/h2&gt;

&lt;p&gt;For variables beyond standard tokens, modify the ViciDial AGI script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Locate AGI handler&lt;/span&gt;
find /usr &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'agi*.agi'&lt;/span&gt; 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Edit &lt;code&gt;/var/lib/asterisk/agi-bin/vicidial_custom.agi&lt;/code&gt; (or similar):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/perl&lt;/span&gt;
&lt;span class="c1"&gt;# Custom variable handler&lt;/span&gt;

&lt;span class="c1"&gt;# Standard input from Asterisk&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="bp"&gt;STDIN&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;chomp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;last&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="nb"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vg"&gt;$_&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^agi_request:\s+(.*)/&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$ENV&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;request&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Add custom variable processing&lt;/span&gt;
&lt;span class="nv"&gt;$lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$ENV&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;agi_arg_1&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;# Query additional data&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT custom_field FROM vicidial_list WHERE list_id = &lt;/span&gt;&lt;span class="si"&gt;$lead_id&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$dbh&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;selectrow_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;# Send back to Asterisk&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SET VARIABLE CUSTOMVAR &lt;/span&gt;&lt;span class="si"&gt;$result&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Script Not Displaying During Calls
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Agent calls connect but script panel shows blank or error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify script_id is correctly assigned:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'TESTCAMP'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Confirm script exists and is active:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;script_id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
                   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'TESTCAMP'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check Asterisk logs for AGI errors:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"agi&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;script"&lt;/span&gt; /var/log/asterisk/messages | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure &lt;code&gt;script_id&lt;/code&gt; foreign key references valid script&lt;/li&gt;
&lt;li&gt;Set script &lt;code&gt;active = 'Y'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Verify campaign has &lt;code&gt;script_screen != 'NONE'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Restart ViciDial service: &lt;code&gt;systemctl restart vicidial&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Variables Not Substituting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Script shows literal tokens like &lt;code&gt;[FIRSTNAME]&lt;/code&gt; instead of actual names.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Check if lead has data in those columns&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'5551234567'&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Populate missing columns in &lt;code&gt;vicidial_list&lt;/code&gt; before call&lt;/li&gt;
&lt;li&gt;Use valid token names from the reference table above&lt;/li&gt;
&lt;li&gt;Check for typos in token names (case-sensitive in some versions)&lt;/li&gt;
&lt;li&gt;Verify lead_id is correctly passed to script parser&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Script Rendering Issues (HTML Scripts)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; HTML script displays as raw markup or styling is broken.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test script in a text editor for syntax errors:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Escape single quotes in SQL when testing locally&lt;/span&gt;
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; cron &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT script_text FROM vicidial_scripts WHERE script_id = 5&lt;/span&gt;&lt;span class="se"&gt;\G&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check browser console for JavaScript errors: Press F12 in agent screen&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Close all HTML tags properly&lt;/li&gt;
&lt;li&gt;Escape single quotes in SQL as &lt;code&gt;''&lt;/code&gt; (double apostrophe)&lt;/li&gt;
&lt;li&gt;Avoid inline event handlers (onclick, onload) — use CSS/JS in agent interface&lt;/li&gt;
&lt;li&gt;Test with simple HTML first, then add complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance Degradation with Large Scripts
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Agent screen becomes slow when script loads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Monitor browser performance&lt;/span&gt;
&lt;span class="c"&gt;# In agent screen, press F12 → Network tab → refresh&lt;/span&gt;
&lt;span class="c"&gt;# Look for script load times &amp;gt; 2 seconds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Compress script text (remove extra whitespace)&lt;/li&gt;
&lt;li&gt;Limit script size to &amp;lt;30 KB&lt;/li&gt;
&lt;li&gt;Use lazy-loading for sections with JavaScript&lt;/li&gt;
&lt;li&gt;Cache scripts on agent machine with &lt;code&gt;Cache-Control&lt;/code&gt; headers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Scripts Reverting After Restart
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Script changes work temporarily but disappear after reboot.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if changes were committed to database&lt;/span&gt;
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; cron &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT script_id, date_modified FROM vicidial_scripts WHERE script_name = 'Test Script';"&lt;/span&gt;

&lt;span class="c"&gt;# Check if backup/restore is running&lt;/span&gt;
ps aux | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; backup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure MySQL is configured to persist data (not running in-memory)&lt;/li&gt;
&lt;li&gt;Verify &lt;code&gt;/var/lib/mysql&lt;/code&gt; is on persistent storage&lt;/li&gt;
&lt;li&gt;Disable auto-restore scripts if they exist in cron jobs&lt;/li&gt;
&lt;li&gt;Manually flush/commit database after changes:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; cron &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"FLUSH TABLES WITH READ LOCK; UNLOCK TABLES;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Agent Sees Wrong Script
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Agent on campaign A sees script from campaign B.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Check what script is assigned to agent's current campaign&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;script_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;script_name&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_user_campaigns&lt;/span&gt; &lt;span class="n"&gt;uc&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;uc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_scripts&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;script_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;script_id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'agent1'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify campaign assignment in admin panel&lt;/li&gt;
&lt;li&gt;Clear browser cache: &lt;code&gt;Ctrl+Shift+Delete&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reload agent screen: &lt;code&gt;Ctrl+F5&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check for conflicting user-level script overrides:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script_override&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'agent1'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Configuring ViciDial agent call scripts involves:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Database Design&lt;/strong&gt; — Understanding the &lt;code&gt;vicidial_scripts&lt;/code&gt; and campaign linking tables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Script Creation&lt;/strong&gt; — Writing scripts with token variables and HTML formatting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Campaign Assignment&lt;/strong&gt; — Linking scripts via MySQL or web admin interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variable Substitution&lt;/strong&gt; — Using tokens like &lt;code&gt;[FIRSTNAME]&lt;/code&gt;, &lt;code&gt;[PHONE]&lt;/code&gt; to inject lead data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing&lt;/strong&gt; — Validating script display and variable substitution in live calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimization&lt;/strong&gt; — Managing script size and performance for production environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Troubleshooting&lt;/strong&gt; — Diagnosing common issues with display, variables, and persistence&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Production best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Always backup&lt;/strong&gt; scripts before bulk updates using mysqldump&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version control&lt;/strong&gt; by including version numbers in script names&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep scripts under 30 KB&lt;/strong&gt; for optimal browser performance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test in staging&lt;/strong&gt; before deploying to live campaigns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor logs&lt;/strong&gt; (&lt;code&gt;/var/log/asterisk/messages&lt;/code&gt;) for AGI-related errors&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document token usage&lt;/strong&gt; for your team to avoid confusion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use HTML formatting&lt;/strong&gt; sparingly — prioritize readability over aesthetics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule reviews&lt;/strong&gt; quarterly to archive outdated scripts and improve active ones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With scripts properly configured, your agents will have consistent, data-driven guidance during every customer interaction, improving call quality and compliance across your contact center operations.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sip</category>
      <category>voip</category>
    </item>
    <item>
      <title>How to Configure ViciDial Caller ID (CID) Settings Per Campaign</title>
      <dc:creator>Moisi Trungu</dc:creator>
      <pubDate>Wed, 07 Oct 2026 03:30:02 +0000</pubDate>
      <link>https://dev.to/moisi_trungu_31647b7ac300/how-to-configure-vicidial-caller-id-cid-settings-per-campaign-2l3c</link>
      <guid>https://dev.to/moisi_trungu_31647b7ac300/how-to-configure-vicidial-caller-id-cid-settings-per-campaign-2l3c</guid>
      <description>&lt;h1&gt;
  
  
  How to Configure ViciDial Caller ID (CID) Settings Per Campaign
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Master per-campaign Caller ID configuration in ViciDial/Asterisk to control outbound number presentation, dynamic CID routing, and compliance-driven number assignment across multiple campaigns.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you begin configuring ViciDial Caller ID settings per campaign, ensure the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ViciDial 2.14+ installed and running (this tutorial covers 2.14-2.17)&lt;/li&gt;
&lt;li&gt;Asterisk 11+ compiled with ViciDial patches&lt;/li&gt;
&lt;li&gt;Direct database access (MySQL/MariaDB) to the asterisk database&lt;/li&gt;
&lt;li&gt;SSH access to the ViciDial server with sudo privileges&lt;/li&gt;
&lt;li&gt;Carrier/trunk configuration already established with available DID numbers&lt;/li&gt;
&lt;li&gt;Understanding of basic ViciDial campaign structure and dialplan concepts&lt;/li&gt;
&lt;li&gt;PHP web admin access (&lt;code&gt;/vicidial/admin.php&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify your ViciDial version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; asterisk &lt;span class="nb"&gt;cat&lt;/span&gt; /etc/asterisk/vici_domains.conf | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check Asterisk is running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core show version"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Understanding ViciDial Caller ID Architecture
&lt;/h2&gt;

&lt;p&gt;ViciDial handles Caller ID assignment through a hierarchical system with multiple fallback points. Understanding this chain is critical for proper configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  The CID Assignment Priority Chain
&lt;/h3&gt;

&lt;p&gt;When an agent dials a lead in ViciDial, the system determines which number to present in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Campaign-level CID&lt;/strong&gt; (if configured in campaign settings)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User/Agent-level CID&lt;/strong&gt; (if assigned to the specific agent)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Carrier CID&lt;/strong&gt; (numbers assigned to the outbound carrier/trunk)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server default CID&lt;/strong&gt; (fallback number configured at server level)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trunk/Channel default&lt;/strong&gt; (last resort from Asterisk trunk config)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This hierarchy allows fine-grained control. A campaign can override agent settings, but agents can still have their own CID if the campaign isn't strict.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Database Tables
&lt;/h3&gt;

&lt;p&gt;Three primary tables govern CID behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;vicidial_campaigns&lt;/strong&gt; — campaign-wide CID settings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;vicidial_users&lt;/strong&gt; — agent-specific CID overrides&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;vicidial_carrier_log&lt;/strong&gt; — outbound carrier trunk configurations&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Accessing Campaign CID Settings via Web Admin
&lt;/h2&gt;

&lt;p&gt;The quickest method for most ViciDial administrators is through the web interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Navigate to Campaign Configuration
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Log in to &lt;code&gt;/vicidial/admin.php&lt;/code&gt; as a user with campaign edit permissions&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Campaigns&lt;/strong&gt; → &lt;strong&gt;Modify Campaign&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select your target campaign from the dropdown&lt;/li&gt;
&lt;li&gt;Look for &lt;strong&gt;Caller ID Number&lt;/strong&gt; field near the top of the form&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 2: Set Campaign-Level CID
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Caller ID Number&lt;/strong&gt; field accepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Static number:&lt;/strong&gt; &lt;code&gt;15551234567&lt;/code&gt; — all calls from this campaign present this number&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Carrier default:&lt;/strong&gt; Leave blank — use the trunk/carrier's assigned DID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Variable assignment:&lt;/strong&gt; See dynamic section below&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Campaign Name: Sales_Outbound_US
Caller ID Number: 15551234567
Caller ID Name: Acme Sales
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Save and Verify
&lt;/h3&gt;

&lt;p&gt;Click &lt;strong&gt;SUBMIT&lt;/strong&gt; to save. The web interface commits directly to the &lt;code&gt;vicidial_campaigns&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;Verify in MySQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cid_name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Sales_Outbound_US'&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="na"&gt;campaign_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="err"&gt;     &lt;/span&gt;&lt;span class="na"&gt;campaign_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sales_Outbound_US&lt;/span&gt;
        &lt;span class="s"&gt;outbound_cid&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15551234567&lt;/span&gt;
           &lt;span class="na"&gt;cid_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Acme Sales&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Database-Level CID Configuration
&lt;/h2&gt;

&lt;p&gt;For advanced scenarios or bulk updates, modify the database directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Direct Campaign CID Update
&lt;/h3&gt;

&lt;p&gt;Update a single campaign:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'15551234567'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cid_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Acme Sales'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the change immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cid_name&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Bulk Update Multiple Campaigns
&lt;/h3&gt;

&lt;p&gt;Update all campaigns with a specific status or prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'16175550123'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cid_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Boston Office'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'Boston_%'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check how many rows were affected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'Boston_%'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Agent-Level CID Override
&lt;/h3&gt;

&lt;p&gt;Override campaign CID for specific agents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'15559876543'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cid_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'John Smith'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'john.smith'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;user_level&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cid_name&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'john.smith'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Agent-level CID only applies if the campaign doesn't have a strict CID policy. Check the campaign's &lt;code&gt;outbound_cid&lt;/code&gt; field—if set, it overrides agents.&lt;/p&gt;

&lt;h3&gt;
  
  
  View Current CID Configuration Across Campaigns
&lt;/h3&gt;

&lt;p&gt;Generate a report of all campaign CID settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cid_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;campaign_type&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Y'&lt;/span&gt; 
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Asterisk Dialplan Integration
&lt;/h2&gt;

&lt;p&gt;The actual CID presentation happens in Asterisk dialplan. ViciDial uses extension contexts generated from database records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reviewing Generated Dialplan
&lt;/h3&gt;

&lt;p&gt;ViciDial writes dialplan on each service restart. The outbound campaign dialplan is in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/asterisk/extensions-vicidial.conf | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 20 &lt;span class="s2"&gt;"exten =&amp;gt; 8,1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for &lt;code&gt;Set(CALLERID(num)=&lt;/code&gt; statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="m"&gt;8&lt;/span&gt;,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;num&lt;/span&gt;)=&lt;span class="m"&gt;15551234567&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="m"&gt;8&lt;/span&gt;,&lt;span class="m"&gt;2&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;name&lt;/span&gt;)=&lt;span class="n"&gt;Acme&lt;/span&gt; &lt;span class="n"&gt;Sales&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="m"&gt;8&lt;/span&gt;,&lt;span class="m"&gt;3&lt;/span&gt;,&lt;span class="n"&gt;Dial&lt;/span&gt;(...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Manual Dialplan Override (Advanced)
&lt;/h3&gt;

&lt;p&gt;For custom CID logic, edit &lt;code&gt;/etc/asterisk/extensions-custom.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;internal&lt;/span&gt;-&lt;span class="n"&gt;custom&lt;/span&gt;]
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;NoOp&lt;/span&gt;(&lt;span class="n"&gt;Custom&lt;/span&gt; &lt;span class="n"&gt;CID&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;campaign&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;num&lt;/span&gt;)=&lt;span class="m"&gt;15551234567&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;name&lt;/span&gt;)=&lt;span class="n"&gt;Acme&lt;/span&gt; &lt;span class="n"&gt;Sales&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Goto&lt;/span&gt;(&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;internal&lt;/span&gt;,${&lt;span class="n"&gt;EXTEN&lt;/span&gt;},&lt;span class="m"&gt;1&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload the dialplan after edits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"dialplan reload"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Custom dialplan changes will be overwritten on next ViciDial service restart. Use only for testing or if you can regenerate extensions programmatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic CID Assignment per Campaign
&lt;/h2&gt;

&lt;p&gt;For scenarios requiring different CID numbers based on area codes, time of day, or lead data, use Asterisk AstDB or a custom script.&lt;/p&gt;

&lt;h3&gt;
  
  
  Method 1: Using Asterisk AstDB
&lt;/h3&gt;

&lt;p&gt;Store CID mappings in AstDB (Asterisk internal database):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"database put CAMPAIGN_CID campaign_1 15551234567"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"database put CAMPAIGN_CID campaign_2 16175550123"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"database put CAMPAIGN_CID campaign_3 12125559999"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"database show CAMPAIGN_CID"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/CAMPAIGN_CID/campaign_1                             : 15551234567
/CAMPAIGN_CID/campaign_2                             : 16175550123
/CAMPAIGN_CID/campaign_3                             : 12125559999
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add to &lt;code&gt;/etc/asterisk/extensions-custom.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;internal&lt;/span&gt;-&lt;span class="n"&gt;custom&lt;/span&gt;]
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;NoOp&lt;/span&gt;(&lt;span class="n"&gt;Dynamic&lt;/span&gt; &lt;span class="n"&gt;CID&lt;/span&gt; &lt;span class="n"&gt;lookup&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; ${&lt;span class="n"&gt;CAMPAIGN_ID&lt;/span&gt;})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CID_NUM&lt;/span&gt;=${&lt;span class="n"&gt;DB&lt;/span&gt;(&lt;span class="n"&gt;CAMPAIGN_CID&lt;/span&gt;/${&lt;span class="n"&gt;CAMPAIGN_ID&lt;/span&gt;})})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;ExecIf&lt;/span&gt;($[&lt;span class="s2"&gt;"${CID_NUM}"&lt;/span&gt; != &lt;span class="s2"&gt;""&lt;/span&gt;]?&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;num&lt;/span&gt;)=${&lt;span class="n"&gt;CID_NUM&lt;/span&gt;}))
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Goto&lt;/span&gt;(&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;internal&lt;/span&gt;,${&lt;span class="n"&gt;EXTEN&lt;/span&gt;},&lt;span class="m"&gt;1&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload dialplan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"dialplan reload"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Method 2: Area Code-Based CID Routing
&lt;/h3&gt;

&lt;p&gt;Route outbound CID based on the lead's area code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Create a lookup table&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;vicidial_cid_routing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cid_route_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;area_code&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;outbound_cid&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;active&lt;/span&gt; &lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Populate with examples&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_cid_routing&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;area_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; 
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'617'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'16175551234'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'212'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'12125559876'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'310'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'13105554321'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add Asterisk logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;internal&lt;/span&gt;-&lt;span class="n"&gt;custom&lt;/span&gt;]
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;NoOp&lt;/span&gt;(&lt;span class="n"&gt;Area&lt;/span&gt;-&lt;span class="n"&gt;code&lt;/span&gt;-&lt;span class="n"&gt;based&lt;/span&gt; &lt;span class="n"&gt;CID&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; ${&lt;span class="n"&gt;EXTEN&lt;/span&gt;:&lt;span class="m"&gt;2&lt;/span&gt;:&lt;span class="m"&gt;3&lt;/span&gt;})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;AREA_CODE&lt;/span&gt;=${&lt;span class="n"&gt;EXTEN&lt;/span&gt;:&lt;span class="m"&gt;2&lt;/span&gt;:&lt;span class="m"&gt;3&lt;/span&gt;})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CID_NUM&lt;/span&gt;=${&lt;span class="n"&gt;ODBC_LOOKUP&lt;/span&gt;(&lt;span class="n"&gt;cid_lookup&lt;/span&gt;,${&lt;span class="n"&gt;CAMPAIGN_ID&lt;/span&gt;},${&lt;span class="n"&gt;AREA_CODE&lt;/span&gt;})})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;ExecIf&lt;/span&gt;($[&lt;span class="s2"&gt;"${CID_NUM}"&lt;/span&gt; != &lt;span class="s2"&gt;""&lt;/span&gt;]?&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;num&lt;/span&gt;)=${&lt;span class="n"&gt;CID_NUM&lt;/span&gt;}))
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Goto&lt;/span&gt;(&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;internal&lt;/span&gt;,${&lt;span class="n"&gt;EXTEN&lt;/span&gt;},&lt;span class="m"&gt;1&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Carrier/Trunk CID Configuration
&lt;/h2&gt;

&lt;p&gt;ViciDial campaigns route through carriers (trunks). Each carrier can have specific DID pools or CID ranges.&lt;/p&gt;

&lt;h3&gt;
  
  
  Viewing Carrier Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;carrier_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;carrier_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;outbound_cid_override&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;active&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_carrier_log&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Assigning DIDs to Carriers
&lt;/h3&gt;

&lt;p&gt;ViciDial manages DIDs through &lt;code&gt;vicidial_did_log&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;did_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;did_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;carrier_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;did_route&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_did_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Y'&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;carrier_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Linking Campaign to Specific Carrier
&lt;/h3&gt;

&lt;p&gt;Some ViciDial installations require explicit carrier assignment per campaign:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Check if your ViciDial version tracks carrier_id in campaigns&lt;/span&gt;
&lt;span class="k"&gt;DESCRIBE&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;grep&lt;/span&gt; &lt;span class="n"&gt;carrier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- If present, assign campaign to carrier&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;carrier_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Not all ViciDial versions have &lt;code&gt;carrier_id&lt;/code&gt; in the campaign table. Check your schema first.&lt;/p&gt;

&lt;h2&gt;
  
  
  SIP Trunk Configuration for CID
&lt;/h2&gt;

&lt;p&gt;The actual SIP trunk configuration in Asterisk determines whether CID is allowed, blocked, or modified by carriers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example SIP Trunk Configuration
&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;/etc/asterisk/sip-vicidial.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;carrier_outbound_trunk&lt;/span&gt;](!)
&lt;span class="n"&gt;type&lt;/span&gt;=&lt;span class="n"&gt;peer&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt;=&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;carriers&lt;/span&gt;
&lt;span class="n"&gt;disallow&lt;/span&gt;=&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt;=&lt;span class="n"&gt;alaw&lt;/span&gt;,&lt;span class="n"&gt;ulaw&lt;/span&gt;,&lt;span class="n"&gt;gsm&lt;/span&gt;
&lt;span class="n"&gt;nat&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;host&lt;/span&gt;=&lt;span class="n"&gt;carrier&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;port&lt;/span&gt;=&lt;span class="m"&gt;5060&lt;/span&gt;
&lt;span class="n"&gt;insecure&lt;/span&gt;=&lt;span class="n"&gt;port&lt;/span&gt;,&lt;span class="n"&gt;invite&lt;/span&gt;

[&lt;span class="n"&gt;carrier_outbound_trunk&lt;/span&gt;](&lt;span class="n"&gt;carrier_outbound_trunk&lt;/span&gt;)
&lt;span class="n"&gt;accountcode&lt;/span&gt;=&lt;span class="n"&gt;OUTBOUND_CARRIER_1&lt;/span&gt;
&lt;span class="n"&gt;fromuser&lt;/span&gt;=&lt;span class="n"&gt;customer_acct&lt;/span&gt;
&lt;span class="n"&gt;fromdomain&lt;/span&gt;=&lt;span class="n"&gt;carrier&lt;/span&gt;.&lt;span class="n"&gt;example&lt;/span&gt;.&lt;span class="n"&gt;com&lt;/span&gt;
&lt;span class="n"&gt;defaultuser&lt;/span&gt;=&lt;span class="n"&gt;customer_acct&lt;/span&gt;
&lt;span class="n"&gt;secret&lt;/span&gt;=&lt;span class="n"&gt;your_sip_password_here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Controlling CID Presentation in SIP INVITE
&lt;/h3&gt;

&lt;p&gt;Add per-trunk CID settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;carrier_outbound_trunk&lt;/span&gt;](!)
; ... &lt;span class="n"&gt;existing&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; ...
&lt;span class="n"&gt;setvar&lt;/span&gt;=&lt;span class="n"&gt;OUTBOUND_CID&lt;/span&gt;=&lt;span class="m"&gt;15551234567&lt;/span&gt;
&lt;span class="n"&gt;setvar&lt;/span&gt;=&lt;span class="n"&gt;OUTBOUND_CID_NAME&lt;/span&gt;=&lt;span class="n"&gt;Acme&lt;/span&gt; &lt;span class="n"&gt;Sales&lt;/span&gt;
; &lt;span class="n"&gt;Allow&lt;/span&gt; &lt;span class="n"&gt;or&lt;/span&gt; &lt;span class="n"&gt;restrict&lt;/span&gt; &lt;span class="n"&gt;CID&lt;/span&gt; &lt;span class="n"&gt;override&lt;/span&gt;
&lt;span class="n"&gt;directmedia&lt;/span&gt;=&lt;span class="n"&gt;no&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pass CID to carrier via dialplan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;num&lt;/span&gt;)=&lt;span class="m"&gt;15551234567&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="m"&gt;2&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;name&lt;/span&gt;)=&lt;span class="n"&gt;Acme&lt;/span&gt; &lt;span class="n"&gt;Sales&lt;/span&gt;)
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="m"&gt;3&lt;/span&gt;,&lt;span class="n"&gt;Dial&lt;/span&gt;(&lt;span class="n"&gt;SIP&lt;/span&gt;/${&lt;span class="n"&gt;EXTEN&lt;/span&gt;}@&lt;span class="n"&gt;carrier_outbound_trunk&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Compliance and Call Recording CID Logging
&lt;/h2&gt;

&lt;p&gt;ViciDial logs all calls with their CID in &lt;code&gt;vicidial_log&lt;/code&gt; and &lt;code&gt;vicidial_closer_log&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifying Logged CID
&lt;/h3&gt;

&lt;p&gt;Check the call log for a specific campaign:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;callerid_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;callerid_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;call_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1700156234.142&lt;/span&gt;
&lt;span class="na"&gt;campaign_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="na"&gt;callerid_number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15551234567&lt;/span&gt;
&lt;span class="na"&gt;callerid_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Acme Sales&lt;/span&gt;
&lt;span class="na"&gt;call_date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2024-01-15 14:30:12&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SALE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Auditing CID Changes Over Time
&lt;/h3&gt;

&lt;p&gt;Create an audit log of CID configuration changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;cid_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;modification_date&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns_archive&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;modification_date&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;vicidial_campaigns_archive&lt;/code&gt; doesn't exist, manually log critical changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"database put CAMPAIGN_CID_AUDIT campaign_1_change_&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%s&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; '15551234567|&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Troubleshooting CID Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Issue 1: CID Not Displaying Correctly to Called Party
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Outbound calls show wrong Caller ID or generic number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check campaign CID setting:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cid_name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Verify agent CID doesn't override:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cid_name&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_users&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'agent_name'&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check Asterisk dialplan generated:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"callerid"&lt;/span&gt; /etc/asterisk/extensions-vicidial.conf | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Monitor live call in Asterisk:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core show channels verbose"&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;OUTGOING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check Asterisk logs:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-100&lt;/span&gt; /var/log/asterisk/messages | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; callerid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Regenerate dialplan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart viciserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or manually reload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"dialplan reload"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 2: CID Rejected by Carrier
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Calls connect but caller ID shows as "Anonymous" or default carrier number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify carrier accepts the CID:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip show peer carrier_outbound_trunk"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Check SIP debug logs:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip set debug on"&lt;/span&gt;
&lt;span class="c"&gt;# Make a test call&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip set debug off"&lt;/span&gt;
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-200&lt;/span&gt; /var/log/asterisk/messages | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"callerid&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;from:"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Contact carrier to verify:

&lt;ul&gt;
&lt;li&gt;CID number is whitelisted&lt;/li&gt;
&lt;li&gt;CID format is correct (E.164: +1NXXXXXXXXX)&lt;/li&gt;
&lt;li&gt;Account permissions allow CID override&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reformat CID to carrier's requirement&lt;/li&gt;
&lt;li&gt;Request CID whitelist approval from carrier&lt;/li&gt;
&lt;li&gt;Use carrier-assigned DID if available&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Issue 3: Incorrect CID on Agent Transfers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; When an agent transfers a call, different CID presents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check if attended transfer preserves CID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core show function REDIRECTING"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify in call logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callerid_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transfer_to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transfer_from&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;HOUR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Configure transfer preservation in &lt;code&gt;/etc/asterisk/extensions-custom.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;REDIRECTING&lt;/span&gt;(&lt;span class="n"&gt;num&lt;/span&gt;)=${&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;num&lt;/span&gt;)})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="m"&gt;2&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;REDIRECTING&lt;/span&gt;(&lt;span class="n"&gt;name&lt;/span&gt;)=${&lt;span class="n"&gt;CALLERID&lt;/span&gt;(&lt;span class="n"&gt;name&lt;/span&gt;)})
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;&lt;span class="m"&gt;91&lt;/span&gt;&lt;span class="n"&gt;XXXXXXXXX&lt;/span&gt;,&lt;span class="m"&gt;3&lt;/span&gt;,&lt;span class="n"&gt;Dial&lt;/span&gt;(&lt;span class="n"&gt;SIP&lt;/span&gt;/&lt;span class="n"&gt;extension&lt;/span&gt;@&lt;span class="n"&gt;asterisk&lt;/span&gt;)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 4: Database Changes Not Reflecting in Calls
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Updated campaign CID in database, but old number still showing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cause:&lt;/strong&gt; Asterisk dialplan is cached in memory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fully restart ViciDial services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop viciserver
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl stop vicidial-Apache
&lt;span class="nb"&gt;sleep &lt;/span&gt;5
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start viciserver
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start vicidial-Apache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify reload completed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"dialplan show from-internal"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; callerid | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 5: CID Shows Campaign Name Instead of Number
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Caller ID displays campaign name (e.g., "Sales_Outbound") instead of phone number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check for misconfiguration in campaign CID field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cid_name&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Sales_Outbound'&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="k"&gt;G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;outbound_cid&lt;/code&gt; contains non-numeric characters, it's misconfigured.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fix the field to contain only digits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;outbound_cid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'15551234567'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'Sales_Outbound'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the format matches E.164:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Should match pattern: 1-15 digits, starting with 1 for US numbers&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"15551234567"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s1"&gt;'^1[0-9]{10,14}$'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Valid"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Invalid"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Debug CID in Real-Time
&lt;/h3&gt;

&lt;p&gt;Enable verbose logging and run a test call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Terminal 1: Enable debug&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core set verbose 5"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip set debug on"&lt;/span&gt;

&lt;span class="c"&gt;# Terminal 2: Monitor logs&lt;/span&gt;
&lt;span class="nb"&gt;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/asterisk/messages | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; callerid

&lt;span class="c"&gt;# Terminal 3: Run test call from agent screen (/agc/vicidial.php)&lt;/span&gt;
&lt;span class="c"&gt;# Monitor both logs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check both agent screen and logs for confirmation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo grep&lt;/span&gt; &lt;span class="s2"&gt;"campaign_id=1"&lt;/span&gt; /var/log/asterisk/messages | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Production Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Document CID Assignments
&lt;/h3&gt;

&lt;p&gt;Maintain a spreadsheet or wiki of all campaign CID assignments:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Campaign ID&lt;/th&gt;
&lt;th&gt;Campaign Name&lt;/th&gt;
&lt;th&gt;Assigned CID&lt;/th&gt;
&lt;th&gt;CID Name&lt;/th&gt;
&lt;th&gt;Carrier&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Sales_US&lt;/td&gt;
&lt;td&gt;15551234567&lt;/td&gt;
&lt;td&gt;Acme Sales&lt;/td&gt;
&lt;td&gt;Carrier A&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Support_CA&lt;/td&gt;
&lt;td&gt;14165559999&lt;/td&gt;
&lt;td&gt;Acme Support&lt;/td&gt;
&lt;td&gt;Carrier B&lt;/td&gt;
&lt;td&gt;Active&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Collections_Test&lt;/td&gt;
&lt;td&gt;15559876543&lt;/td&gt;
&lt;td&gt;Test CID&lt;/td&gt;
&lt;td&gt;Carrier A&lt;/td&gt;
&lt;td&gt;Testing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Use Staging Environment
&lt;/h3&gt;

&lt;p&gt;Test CID changes in a staging ViciDial before production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Backup production campaign settings&lt;/span&gt;
mysqldump &lt;span class="nt"&gt;-u&lt;/span&gt; asterisk &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk vicidial_campaigns &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; campaigns_backup_&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y%m%d&lt;span class="si"&gt;)&lt;/span&gt;.sql

&lt;span class="c"&gt;# Test in staging first&lt;/span&gt;
mysql &lt;span class="nt"&gt;-u&lt;/span&gt; asterisk &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk_stage &amp;lt; test_campaign_cid.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Implement Change Control
&lt;/h3&gt;

&lt;p&gt;Log all manual CID changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create audit log&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] Changed campaign 1 CID from 15551111111 to 15551234567 - Admin: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;whoami&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /var/log/vicidial_cid_changes.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Validate CID Format
&lt;/h3&gt;

&lt;p&gt;Before updating, validate CID numbers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Simple validation script&lt;/span&gt;
validate_cid&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^1[0-9]&lt;span class="o"&gt;{&lt;/span&gt;10,14&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^&lt;span class="se"&gt;\+&lt;/span&gt;1[0-9]&lt;span class="o"&gt;{&lt;/span&gt;10,14&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Valid CID: &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;0
    &lt;span class="k"&gt;else
        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Invalid CID format: &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return &lt;/span&gt;1
    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

validate_cid &lt;span class="s2"&gt;"15551234567"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"OK"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"FAIL"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Monitor CID Effectiveness
&lt;/h3&gt;

&lt;p&gt;Regularly audit that CID is logging correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Daily CID verification report&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;call_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="n"&gt;callerid_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;unique_cids&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_log&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call_date&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;call_date&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Configuring Caller ID per ViciDial campaign requires understanding the CID assignment hierarchy, database configuration, and Asterisk dialplan integration. Key takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use the web admin&lt;/strong&gt; (&lt;code&gt;/vicidial/admin.php&lt;/code&gt;) for simple, one-off campaign CID changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access the database&lt;/strong&gt; directly for bulk updates or complex scenarios&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand the hierarchy:&lt;/strong&gt; Campaign CID overrides agent CID, which overrides carrier default&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify in Asterisk:&lt;/strong&gt; Monitor dialplan generation and reload to ensure changes take effect&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test before production:&lt;/strong&gt; Use staging environments and maintain change logs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit regularly:&lt;/strong&gt; Check &lt;code&gt;vicidial_log&lt;/code&gt; to confirm CID is presenting correctly to called parties&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle carrier restrictions:&lt;/strong&gt; Some carriers whitelist or restrict CID numbers; coordinate with your carrier&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Troubleshoot methodically:&lt;/strong&gt; Check database → dialplan → Asterisk logs → carrier support&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For dynamic CID assignment beyond static campaign settings, use AstDB lookups or custom dialplan logic tied to area codes, lead data, or other routing variables. Always maintain backups before bulk changes and test in a staging environment whenever possible.&lt;/p&gt;

&lt;p&gt;The configuration is now complete and ready for production use. Monitor call logs regularly to ensure CID compliance and effectiveness.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sip</category>
      <category>voip</category>
    </item>
    <item>
      <title>How to Set Up ViciDial Scheduled Callbacks — Agent &amp; Campaign Level</title>
      <dc:creator>Moisi Trungu</dc:creator>
      <pubDate>Tue, 06 Oct 2026 03:30:04 +0000</pubDate>
      <link>https://dev.to/moisi_trungu_31647b7ac300/how-to-set-up-vicidial-scheduled-callbacks-agent-campaign-level-3ihm</link>
      <guid>https://dev.to/moisi_trungu_31647b7ac300/how-to-set-up-vicidial-scheduled-callbacks-agent-campaign-level-3ihm</guid>
      <description>&lt;h1&gt;
  
  
  How to Set Up ViciDial Scheduled Callbacks — Agent &amp;amp; Campaign Level
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Master both agent-initiated and campaign-level scheduled callbacks in ViciDial, including database configuration, Asterisk dialplan setup, and production troubleshooting.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before implementing scheduled callbacks, ensure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ViciDial&lt;/strong&gt; v2.14 or later installed and running&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asterisk&lt;/strong&gt; 13+ with proper SIP configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MySQL/MariaDB&lt;/strong&gt; access with direct database privileges&lt;/li&gt;
&lt;li&gt;Administrator access to &lt;code&gt;/vicidial/admin.php&lt;/code&gt; web interface&lt;/li&gt;
&lt;li&gt;SSH access to the ViciDial server for file editing&lt;/li&gt;
&lt;li&gt;Basic understanding of ViciDial call flow and campaign structure&lt;/li&gt;
&lt;li&gt;At least one active &lt;strong&gt;campaign&lt;/strong&gt; and &lt;strong&gt;inbound group&lt;/strong&gt; configured&lt;/li&gt;
&lt;li&gt;Asterisk CLI access via &lt;code&gt;asterisk -rx&lt;/code&gt; commands&lt;/li&gt;
&lt;li&gt;Daily cron jobs already configured for callback processing (&lt;code&gt;/usr/share/astguiclient/&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding ViciDial Callback Types
&lt;/h2&gt;

&lt;p&gt;ViciDial supports two primary callback mechanisms:&lt;/p&gt;

&lt;h3&gt;
  
  
  Agent-Level Scheduled Callbacks
&lt;/h3&gt;

&lt;p&gt;Callbacks initiated by agents during live calls via the agent screen (&lt;code&gt;/agc/vicidial.php&lt;/code&gt;). The agent sets a callback time, and the system attempts to reach the lead at the scheduled time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Campaign-Level Scheduled Callbacks
&lt;/h3&gt;

&lt;p&gt;Callbacks triggered automatically by campaign rules, IVR results, or system events. These run on a larger scale and respect campaign-wide settings.&lt;/p&gt;

&lt;p&gt;Both types store data in the &lt;code&gt;vicidial_log&lt;/code&gt; and &lt;code&gt;vicidial_closer_log&lt;/code&gt; tables but follow different processing workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Section 1: Database Configuration for Callbacks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1.1: Verify Core Tables Exist
&lt;/h3&gt;

&lt;p&gt;Log into your ViciDial MySQL database and confirm callback-related tables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; root &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SHOW TABLES LIKE '%vicidial%';"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"(log|callback)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see tables including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;vicidial_log&lt;/code&gt; (main call log)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vicidial_closer_log&lt;/code&gt; (agent notes and callbacks)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vicidial_list&lt;/code&gt; (lead records)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vicidial_callbacks&lt;/code&gt; (scheduled callback queue)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 1.2: Create or Verify Vicidial_Callbacks Table
&lt;/h3&gt;

&lt;p&gt;If the &lt;code&gt;vicidial_callbacks&lt;/code&gt; table doesn't exist, create it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`vicidial_callbacks`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;`callback_id`&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`lead_id`&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`campaign_id`&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`callback_time`&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`callback_type`&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'AGENT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`user_id`&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`phone_number`&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`notes`&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`status`&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'PENDING'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`created_date`&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`processed_date`&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`attempt_count`&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`max_attempts`&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`callback_id`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="nv"&gt;`idx_campaign_time`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`campaign_id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;`callback_time`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="nv"&gt;`idx_lead_campaign`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`lead_id`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;`campaign_id`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="nv"&gt;`idx_status`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`status`&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="nv"&gt;`idx_callback_time`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`callback_time`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;CHARSET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1.3: Add Callback Fields to Vicidial_List
&lt;/h3&gt;

&lt;p&gt;Ensure the &lt;code&gt;vicidial_list&lt;/code&gt; table has callback-related columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`vicidial_list`&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="nv"&gt;`callback_date`&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`vicidial_list`&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="nv"&gt;`callback_type`&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'AGENT'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`vicidial_list`&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="nv"&gt;`last_callback_attempt`&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_callback_date&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback_date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1.4: Extend Vicidial_Closer_Log for Callback Tracking
&lt;/h3&gt;

&lt;p&gt;Add callback metadata to agent notes table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`vicidial_closer_log`&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="nv"&gt;`scheduled_callback`&lt;/span&gt; &lt;span class="nb"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`vicidial_closer_log`&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="nv"&gt;`callback_phone`&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`vicidial_closer_log`&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="nv"&gt;`callback_notes`&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_scheduled_callback&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vicidial_closer_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_callback&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Section 2: Campaign-Level Callback Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 2.1: Access Campaign Settings
&lt;/h3&gt;

&lt;p&gt;Log into the ViciDial admin panel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://YOUR_VICIDIAL_IP/vicidial/admin.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to: &lt;strong&gt;Admin → Manage Campaigns&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Select your target campaign and scroll to the &lt;strong&gt;"Callback Settings"&lt;/strong&gt; section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2.2: Configure Campaign Callback Parameters
&lt;/h3&gt;

&lt;p&gt;Set the following fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Callback Enabled&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;YES&lt;/td&gt;
&lt;td&gt;Activates callback feature for the campaign&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Callback Attempt Limit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Maximum retry attempts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Callback Time Window&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;09:00-17:00&lt;/td&gt;
&lt;td&gt;Hours when callbacks should be attempted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Callback Interval&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;300&lt;/td&gt;
&lt;td&gt;Seconds between retry attempts (300 = 5 min)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Callback Lead Status&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CBHOLD&lt;/td&gt;
&lt;td&gt;Lead status after callback is scheduled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Callback Dial Timeout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;Seconds to wait for answer before hanging up&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Step 2.3: Update Campaign in Database
&lt;/h3&gt;

&lt;p&gt;Alternatively, update directly via SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;SET&lt;/span&gt; 
  &lt;span class="n"&gt;use_callbacks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Y'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;callback_attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;callback_interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;callback_leadstatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'CBHOLD'&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'YOUR_CAMPAIGN_ID'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;use_callbacks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_attempts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_interval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_leadstatus&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_campaigns&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'YOUR_CAMPAIGN_ID'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Section 3: Agent-Level Callback Setup via Agent Screen
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 3.1: Understanding the Agent Screen Callback Button
&lt;/h3&gt;

&lt;p&gt;When an agent is on a live call in &lt;code&gt;/agc/vicidial.php&lt;/code&gt;, they can click the &lt;strong&gt;"Schedule Callback"&lt;/strong&gt; button. This triggers a JavaScript modal for entering callback details.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3.2: Modify Agent Screen for Callback Display
&lt;/h3&gt;

&lt;p&gt;Edit the agent application configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /var/www/html/agc/vicidial.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Locate the callback button section (typically around line 800-1200) and ensure this code block exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;schedule_callback_form&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;callback_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter callback time (YYYY-MM-DD HH:MM:SS):&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;callback_phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Callback phone number:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;phone_number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;callback_notes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Callback notes:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;xhr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;XMLHttpRequest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/agc/schedule_callback.php&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRequestHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Callback scheduled successfully!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;log_callback_in_notes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callback_notes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nx"&gt;xhr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;lead_id=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
             &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;callback_time=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
             &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;callback_phone=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback_phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
             &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;callback_notes=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;callback_notes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
             &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;campaign_id=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
             &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;user_id=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;encodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3.3: Create Callback Processing PHP Script
&lt;/h3&gt;

&lt;p&gt;Create a new file for handling agent callback submissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /var/www/html/agc/schedule_callback.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="c1"&gt;// ViciDial Agent Callback Submission Handler&lt;/span&gt;
&lt;span class="nb"&gt;error_reporting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;E_ALL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;ini_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'display_errors'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Security: Verify session&lt;/span&gt;
&lt;span class="nb"&gt;session_start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;isset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;http_response_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Unauthorized'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Database connection&lt;/span&gt;
&lt;span class="nv"&gt;$DB_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$DB_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'vicidial'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$DB_pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'VICIDIAL_DB_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Change this&lt;/span&gt;
&lt;span class="nv"&gt;$DB_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'asterisk'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$mysqli&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;mysqli&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$DB_host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$DB_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$DB_pass&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$DB_name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mysqli&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;connect_error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;http_response_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Database connection failed'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Input validation&lt;/span&gt;
&lt;span class="nv"&gt;$lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;intval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'lead_id'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nv"&gt;$campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$mysqli&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;real_escape_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'campaign_id'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nv"&gt;$callback_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$mysqli&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;real_escape_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'callback_time'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nv"&gt;$callback_phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/[^0-9]/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'callback_phone'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nv"&gt;$callback_notes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$mysqli&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;real_escape_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'callback_notes'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nv"&gt;$user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$mysqli&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;real_escape_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_SESSION&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'user_id'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="c1"&gt;// Validate callback time is in the future&lt;/span&gt;
&lt;span class="nv"&gt;$callback_timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;strtotime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$callback_time&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$callback_timestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;http_response_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Callback time must be in the future'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Insert into vicidial_callbacks table&lt;/span&gt;
&lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INSERT INTO vicidial_callbacks 
          (lead_id, campaign_id, callback_time, callback_type, user_id, phone_number, notes, status) 
          VALUES 
          (&lt;/span&gt;&lt;span class="nv"&gt;$lead_id&lt;/span&gt;&lt;span class="s2"&gt;, '&lt;/span&gt;&lt;span class="nv"&gt;$campaign_id&lt;/span&gt;&lt;span class="s2"&gt;', '&lt;/span&gt;&lt;span class="nv"&gt;$callback_time&lt;/span&gt;&lt;span class="s2"&gt;', 'AGENT', '&lt;/span&gt;&lt;span class="nv"&gt;$user_id&lt;/span&gt;&lt;span class="s2"&gt;', '&lt;/span&gt;&lt;span class="nv"&gt;$callback_phone&lt;/span&gt;&lt;span class="s2"&gt;', '&lt;/span&gt;&lt;span class="nv"&gt;$callback_notes&lt;/span&gt;&lt;span class="s2"&gt;', 'PENDING')"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$mysqli&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Update vicidial_list to mark callback pending&lt;/span&gt;
    &lt;span class="nv"&gt;$update_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"UPDATE vicidial_list 
                    SET callback_date = '&lt;/span&gt;&lt;span class="nv"&gt;$callback_time&lt;/span&gt;&lt;span class="s2"&gt;', 
                        callback_type = 'AGENT',
                        last_callback_attempt = NOW()
                    WHERE lead_id = &lt;/span&gt;&lt;span class="nv"&gt;$lead_id&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$mysqli&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$update_query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Log to closer log for agent reference&lt;/span&gt;
    &lt;span class="nv"&gt;$closer_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"INSERT INTO vicidial_closer_log 
                    (lead_id, campaign_id, call_date, user_id, scheduled_callback, callback_phone, callback_notes) 
                    VALUES 
                    (&lt;/span&gt;&lt;span class="nv"&gt;$lead_id&lt;/span&gt;&lt;span class="s2"&gt;, '&lt;/span&gt;&lt;span class="nv"&gt;$campaign_id&lt;/span&gt;&lt;span class="s2"&gt;', NOW(), '&lt;/span&gt;&lt;span class="nv"&gt;$user_id&lt;/span&gt;&lt;span class="s2"&gt;', '&lt;/span&gt;&lt;span class="nv"&gt;$callback_time&lt;/span&gt;&lt;span class="s2"&gt;', '&lt;/span&gt;&lt;span class="nv"&gt;$callback_phone&lt;/span&gt;&lt;span class="s2"&gt;', '&lt;/span&gt;&lt;span class="nv"&gt;$callback_notes&lt;/span&gt;&lt;span class="s2"&gt;')"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$mysqli&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$closer_query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="nb"&gt;http_response_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'success'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'callback_id'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$mysqli&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;insert_id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;http_response_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;json_encode&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'error'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$mysqli&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$mysqli&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Section 4: Asterisk Dialplan Configuration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 4.1: Create Callback Processing Extension
&lt;/h3&gt;

&lt;p&gt;Edit your Asterisk extensions configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/asterisk/extensions-vicidial.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a dedicated context for callback processing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;callback&lt;/span&gt;-&lt;span class="n"&gt;outbound&lt;/span&gt;]
&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="err"&gt;_&lt;/span&gt;.,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;NoOp&lt;/span&gt;(&lt;span class="n"&gt;ViciDial&lt;/span&gt; &lt;span class="n"&gt;Callback&lt;/span&gt; &lt;span class="n"&gt;Processing&lt;/span&gt; - &lt;span class="n"&gt;Leg&lt;/span&gt;: ${&lt;span class="n"&gt;CALLLEG&lt;/span&gt;})
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;TIMEOUT&lt;/span&gt;(&lt;span class="n"&gt;absolute&lt;/span&gt;)=&lt;span class="m"&gt;3600&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Log&lt;/span&gt;(&lt;span class="n"&gt;NOTICE&lt;/span&gt;, &lt;span class="n"&gt;Processing&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;lead&lt;/span&gt;: ${&lt;span class="n"&gt;lead_id&lt;/span&gt;})
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Dial&lt;/span&gt;(&lt;span class="n"&gt;SIP&lt;/span&gt;/${&lt;span class="n"&gt;callback_phone&lt;/span&gt;}@&lt;span class="n"&gt;callback&lt;/span&gt;-&lt;span class="n"&gt;carrier&lt;/span&gt;,&lt;span class="m"&gt;45&lt;/span&gt;,&lt;span class="n"&gt;tTkKwW&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;GoTo&lt;/span&gt;(&lt;span class="n"&gt;callback&lt;/span&gt;-&lt;span class="n"&gt;result&lt;/span&gt;-${&lt;span class="n"&gt;DIALSTATUS&lt;/span&gt;},&lt;span class="m"&gt;1&lt;/span&gt;)

&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;callback&lt;/span&gt;-&lt;span class="n"&gt;result&lt;/span&gt;-&lt;span class="n"&gt;ANSWER&lt;/span&gt;,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;NoOp&lt;/span&gt;(&lt;span class="n"&gt;Callback&lt;/span&gt; &lt;span class="n"&gt;answered&lt;/span&gt; &lt;span class="n"&gt;successfully&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;DB&lt;/span&gt;(&lt;span class="n"&gt;vicidial&lt;/span&gt;/&lt;span class="n"&gt;callback&lt;/span&gt;/${&lt;span class="n"&gt;lead_id&lt;/span&gt;})=&lt;span class="n"&gt;ANSWERED&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;callback_status&lt;/span&gt;=&lt;span class="n"&gt;ANSWERED&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Hangup&lt;/span&gt;()

&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;callback&lt;/span&gt;-&lt;span class="n"&gt;result&lt;/span&gt;-&lt;span class="n"&gt;BUSY&lt;/span&gt;,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;NoOp&lt;/span&gt;(&lt;span class="n"&gt;Callback&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="n"&gt;busy&lt;/span&gt; - &lt;span class="n"&gt;will&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;DB&lt;/span&gt;(&lt;span class="n"&gt;vicidial&lt;/span&gt;/&lt;span class="n"&gt;callback&lt;/span&gt;/${&lt;span class="n"&gt;lead_id&lt;/span&gt;})=&lt;span class="n"&gt;BUSY&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Hangup&lt;/span&gt;()

&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;callback&lt;/span&gt;-&lt;span class="n"&gt;result&lt;/span&gt;-&lt;span class="n"&gt;NOANSWER&lt;/span&gt;,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;NoOp&lt;/span&gt;(&lt;span class="n"&gt;No&lt;/span&gt; &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;DB&lt;/span&gt;(&lt;span class="n"&gt;vicidial&lt;/span&gt;/&lt;span class="n"&gt;callback&lt;/span&gt;/${&lt;span class="n"&gt;lead_id&lt;/span&gt;})=&lt;span class="n"&gt;NOANSWER&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Hangup&lt;/span&gt;()

&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;callback&lt;/span&gt;-&lt;span class="n"&gt;result&lt;/span&gt;-&lt;span class="n"&gt;CHANUNAVAIL&lt;/span&gt;,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;NoOp&lt;/span&gt;(&lt;span class="n"&gt;Channel&lt;/span&gt; &lt;span class="n"&gt;unavailable&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Set&lt;/span&gt;(&lt;span class="n"&gt;DB&lt;/span&gt;(&lt;span class="n"&gt;vicidial&lt;/span&gt;/&lt;span class="n"&gt;callback&lt;/span&gt;/${&lt;span class="n"&gt;lead_id&lt;/span&gt;})=&lt;span class="n"&gt;FAILED&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Hangup&lt;/span&gt;()

&lt;span class="n"&gt;exten&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;h&lt;/span&gt;,&lt;span class="m"&gt;1&lt;/span&gt;,&lt;span class="n"&gt;NoOp&lt;/span&gt;(&lt;span class="n"&gt;Hangup&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="n"&gt;for&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;)
 &lt;span class="n"&gt;same&lt;/span&gt; =&amp;gt; &lt;span class="n"&gt;n&lt;/span&gt;,&lt;span class="n"&gt;Log&lt;/span&gt;(&lt;span class="n"&gt;NOTICE&lt;/span&gt;, &lt;span class="n"&gt;Callback&lt;/span&gt; &lt;span class="n"&gt;hangup&lt;/span&gt; - &lt;span class="n"&gt;Status&lt;/span&gt;: ${&lt;span class="n"&gt;callback_status&lt;/span&gt;})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4.2: Update SIP Configuration for Callback Carrier
&lt;/h3&gt;

&lt;p&gt;Edit the SIP configuration to allow callback outbound routing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/asterisk/sip-vicidial.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add or modify the callback carrier section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;[&lt;span class="n"&gt;callback&lt;/span&gt;-&lt;span class="n"&gt;carrier&lt;/span&gt;]
&lt;span class="n"&gt;type&lt;/span&gt;=&lt;span class="n"&gt;peer&lt;/span&gt;
&lt;span class="n"&gt;host&lt;/span&gt;=&lt;span class="n"&gt;YOUR_CARRIER_IP_OR_HOSTNAME&lt;/span&gt;
&lt;span class="n"&gt;port&lt;/span&gt;=&lt;span class="m"&gt;5060&lt;/span&gt;
&lt;span class="n"&gt;secret&lt;/span&gt;=&lt;span class="n"&gt;YOUR_SIP_SECRET&lt;/span&gt;
&lt;span class="n"&gt;username&lt;/span&gt;=&lt;span class="n"&gt;YOUR_SIP_USERNAME&lt;/span&gt;
&lt;span class="n"&gt;fromuser&lt;/span&gt;=&lt;span class="n"&gt;YOUR_SIP_USERNAME&lt;/span&gt;
&lt;span class="n"&gt;fromdomain&lt;/span&gt;=&lt;span class="n"&gt;YOUR_SIP_DOMAIN&lt;/span&gt;
&lt;span class="n"&gt;dtmfmode&lt;/span&gt;=&lt;span class="n"&gt;rfc2833&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt;=&lt;span class="n"&gt;from&lt;/span&gt;-&lt;span class="n"&gt;internal&lt;/span&gt;
&lt;span class="n"&gt;disallow&lt;/span&gt;=&lt;span class="n"&gt;all&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt;=&lt;span class="n"&gt;ulaw&lt;/span&gt;
&lt;span class="n"&gt;allow&lt;/span&gt;=&lt;span class="n"&gt;gsm&lt;/span&gt;
&lt;span class="n"&gt;canreinvite&lt;/span&gt;=&lt;span class="n"&gt;no&lt;/span&gt;
&lt;span class="n"&gt;nat&lt;/span&gt;=&lt;span class="n"&gt;force_rport&lt;/span&gt;,&lt;span class="n"&gt;comedia&lt;/span&gt;
&lt;span class="n"&gt;qualify&lt;/span&gt;=&lt;span class="n"&gt;yes&lt;/span&gt;
&lt;span class="n"&gt;qualifyfreq&lt;/span&gt;=&lt;span class="m"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4.3: Reload Asterisk Configuration
&lt;/h3&gt;

&lt;p&gt;After editing configuration files, reload Asterisk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip reload"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"dialplan reload"&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core reload"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip show peers"&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;callback
&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"dialplan show callback-outbound"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Section 5: Cron Jobs and Scheduled Processing
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 5.1: Create Callback Processing Script
&lt;/h3&gt;

&lt;p&gt;Create the main callback processor script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /usr/share/astguiclient/vicidial_callbacks_processor.pl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following Perl script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/perl&lt;/span&gt;
&lt;span class="c1"&gt;# ViciDial Scheduled Callbacks Processor&lt;/span&gt;
&lt;span class="c1"&gt;# Runs every 60 seconds via cron&lt;/span&gt;
&lt;span class="c1"&gt;# Usage: /usr/share/astguiclient/vicidial_callbacks_processor.pl&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;strict&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;DBI&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nv"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;DateTime::Format::&lt;/span&gt;&lt;span class="nv"&gt;MySQL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$DB_host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$DB_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vicidial&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$DB_pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;VICIDIAL_DB_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt; &lt;span class="c1"&gt;# Change this&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$DB_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;asterisk&lt;/span&gt;&lt;span class="p"&gt;';&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$dbh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;DBI&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;DBI:mysql:&lt;/span&gt;&lt;span class="si"&gt;$DB_name&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;$DB_host&lt;/span&gt;&lt;span class="p"&gt;",&lt;/span&gt; &lt;span class="nv"&gt;$DB_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$DB_pass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;die&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cannot connect to database: &lt;/span&gt;&lt;span class="si"&gt;$DBI&lt;/span&gt;&lt;span class="s2"&gt;::errstr&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$now_mysql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;DateTime::Format::&lt;/span&gt;&lt;span class="nv"&gt;MySQL&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;format_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$now&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;# Query pending callbacks due within the next 60 seconds&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT cb.callback_id, cb.lead_id, cb.campaign_id, cb.phone_number, 
                    cb.callback_time, cb.attempt_count, cb.max_attempts, vl.phone_number as lead_phone
             FROM vicidial_callbacks cb
             JOIN vicidial_list vl ON cb.lead_id = vl.lead_id
             WHERE cb.status = 'PENDING'
             AND cb.callback_time &amp;lt;= DATE_ADD(NOW(), INTERVAL 60 SECOND)
             AND cb.callback_time &amp;gt; NOW()
             AND cb.attempt_count &amp;lt; cb.max_attempts
             ORDER BY cb.callback_time ASC
             LIMIT 50&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$sth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$dbh&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$sth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;die&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Query failed: &lt;/span&gt;&lt;span class="si"&gt;$DBI&lt;/span&gt;&lt;span class="s2"&gt;::errstr&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;

&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$callback_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$sth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;fetchrow_hashref&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$callback_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;callback_id&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$campaign_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$phone_number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$attempt_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;attempt_count&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$max_attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;# Execute Asterisk originate command&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$asterisk_cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sx"&gt;qq{
        Originate(SIP/$phone_number\@callback-carrier,exten,callback-outbound,$phone_number,1,,)
    }&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;/usr/sbin/asterisk -rx &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="si"&gt;$asterisk_cmd&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;

    &lt;span class="c1"&gt;# Update attempt count&lt;/span&gt;
    &lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$update_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE vicidial_callbacks 
                       SET attempt_count = attempt_count + 1,
                           processed_date = NOW()
                       WHERE callback_id = &lt;/span&gt;&lt;span class="si"&gt;$callback_id&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;

    &lt;span class="nv"&gt;$dbh&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$update_query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;warn&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to update callback: &lt;/span&gt;&lt;span class="si"&gt;$DBI&lt;/span&gt;&lt;span class="s2"&gt;::errstr&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;

    &lt;span class="c1"&gt;# Log the callback attempt&lt;/span&gt;
    &lt;span class="nb"&gt;system&lt;/span&gt;&lt;span class="p"&gt;("&lt;/span&gt;&lt;span class="s2"&gt;logger -t vicidial_callbacks 'Callback initiated: lead_id=&lt;/span&gt;&lt;span class="si"&gt;$lead_id&lt;/span&gt;&lt;span class="s2"&gt;, phone=&lt;/span&gt;&lt;span class="si"&gt;$phone_number&lt;/span&gt;&lt;span class="s2"&gt;, attempt=&lt;/span&gt;&lt;span class="p"&gt;"&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$attempt_count&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;$max_attempts&lt;/span&gt;&lt;span class="s2"&gt;'&lt;/span&gt;&lt;span class="p"&gt;");&lt;/span&gt;

    &lt;span class="nv"&gt;$callback_count&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Mark expired callbacks as EXPIRED&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$expire_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE vicidial_callbacks 
                   SET status = 'EXPIRED'
                   WHERE status = 'PENDING'
                   AND callback_time &amp;lt; DATE_SUB(NOW(), INTERVAL 24 HOUR)&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;

&lt;span class="nv"&gt;$dbh&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$expire_query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;# Mark max-attempt callbacks as FAILED&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$fail_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UPDATE vicidial_callbacks 
                 SET status = 'FAILED'
                 WHERE status = 'PENDING'
                 AND attempt_count &amp;gt;= max_attempts&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;

&lt;span class="nv"&gt;$dbh&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fail_query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$dbh&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make the script executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;755 /usr/share/astguiclient/vicidial_callbacks_processor.pl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5.2: Add Cron Job for Callback Processing
&lt;/h3&gt;

&lt;p&gt;Edit the crontab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;crontab &lt;span class="nt"&gt;-e&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following line to run callback processor every minute:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* * * * * /usr/share/astguiclient/vicidial_callbacks_processor.pl &amp;gt;&amp;gt; /var/log/asterisk/vicidial_callbacks.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5.3: Monitor Callback Processing Logs
&lt;/h3&gt;

&lt;p&gt;Create a log file and monitor it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo touch&lt;/span&gt; /var/log/asterisk/vicidial_callbacks.log
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;asterisk:asterisk /var/log/asterisk/vicidial_callbacks.log
&lt;span class="nb"&gt;sudo chmod &lt;/span&gt;644 /var/log/asterisk/vicidial_callbacks.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitor in real-time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/asterisk/vicidial_callbacks.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Section 6: Testing and Verification
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 6.1: Manual Callback Test via SQL
&lt;/h3&gt;

&lt;p&gt;Insert a test callback 5 minutes in the future:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt; 
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;notes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;VALUES&lt;/span&gt; 
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'TESTCAMP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DATE_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;MINUTE&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'AGENT'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'testuser'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'15551234567'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Test callback'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PENDING'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the insert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;callback_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt_count&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6.2: Monitor Asterisk During Callback
&lt;/h3&gt;

&lt;p&gt;In one terminal, watch Asterisk logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/asterisk/messages | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; callback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In another terminal, trigger the callback processor manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/share/astguiclient/vicidial_callbacks_processor.pl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6.3: Verify Callback Status in Database
&lt;/h3&gt;

&lt;p&gt;Check the status after callback attempt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;callback_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;processed_date&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12345&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;processed_date&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6.4: Check Agent Screen Integration
&lt;/h3&gt;

&lt;p&gt;Log into the agent screen at &lt;code&gt;/agc/vicidial.php&lt;/code&gt; with a test agent account. During a test call, click "Schedule Callback" and verify:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Modal appears with time and phone fields&lt;/li&gt;
&lt;li&gt;Callback is inserted into database&lt;/li&gt;
&lt;li&gt;Entry appears in agent's call notes&lt;/li&gt;
&lt;li&gt;Callback time displays correctly in lead history&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Section 7: Production Optimization
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 7.1: Database Indexing for Performance
&lt;/h3&gt;

&lt;p&gt;Add strategic indexes to improve callback query performance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_callbacks_status_time&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_time&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_callbacks_campaign_time&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_time&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_callbacks_lead&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_list_callback&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_type&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7.2: Rate Limiting for Callback Dialout
&lt;/h3&gt;

&lt;p&gt;Modify the callback processor to respect concurrent call limits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Add to vicidial_callbacks_processor.pl&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$max_concurrent_callbacks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$concurrent_query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT COUNT(*) as active_callbacks 
                       FROM asterisk.vicidial_callbacks 
                       WHERE status = 'PROCESSING'&lt;/span&gt;&lt;span class="p"&gt;";&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$concurrent_sth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$dbh&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;prepare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$concurrent_query&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$concurrent_sth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$concurrent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$concurrent_sth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;fetchrow_hashref&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;active_callbacks&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$concurrent&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nv"&gt;$max_concurrent_callbacks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;# Skip this run if limit reached&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 7.3: Callback Time Zone Handling
&lt;/h3&gt;

&lt;p&gt;Ensure callbacks respect agent/lead time zones:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="k"&gt;COLUMN&lt;/span&gt; &lt;span class="n"&gt;timezone_offset&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'+00:00'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;
&lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;vicidial_list&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lead_id&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt; &lt;span class="c1"&gt;-- Assumes state field maps to timezone&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'+00:00'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update callback processor to account for timezone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$callback_time_utc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;callback_time&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$tz_offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$row&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;timezone_offset&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;my&lt;/span&gt; &lt;span class="nv"&gt;$callback_time_local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;DateTime&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt;from_epoch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;epoch&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;time_zone&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$tz_offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Section 8: Troubleshooting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Issue 1: Callbacks Not Triggering
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Callbacks scheduled but never attempt to dial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check if cron job is running&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;ps aux | &lt;span class="nb"&gt;grep &lt;/span&gt;vicidial_callbacks_processor
&lt;span class="c"&gt;# Check cron logs&lt;/span&gt;
&lt;span class="nb"&gt;sudo grep &lt;/span&gt;CRON /var/log/syslog | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ensure the cron job is active and check file permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;crontab &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;vicidial_callbacks_processor
&lt;span class="nb"&gt;sudo ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /usr/share/astguiclient/vicidial_callbacks_processor.pl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If missing, re-add to crontab as shown in Section 5.2.&lt;/p&gt;

&lt;h3&gt;
  
  
  Issue 2: Database Connection Errors
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; &lt;code&gt;Cannot connect to database&lt;/code&gt; errors in callback logs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Verify database credentials in the Perl script match your installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"DB_pass&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;DB_user"&lt;/span&gt; /usr/share/astguiclient/&lt;span class="k"&gt;*&lt;/span&gt;.pl | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Update credentials in &lt;code&gt;/usr/share/astguiclient/vicidial_callbacks_processor.pl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /usr/share/astguiclient/vicidial_callbacks_processor.pl
&lt;span class="c"&gt;# Edit the DB connection variables at the top&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test connection manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mysql &lt;span class="nt"&gt;-h&lt;/span&gt; localhost &lt;span class="nt"&gt;-u&lt;/span&gt; vicidial &lt;span class="nt"&gt;-p&lt;/span&gt; asterisk &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SELECT COUNT(*) FROM vicidial_callbacks;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 3: Asterisk Originate Command Fails
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Callbacks attempted but SIP channels fail to originate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check Asterisk SIP peer status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip show peers"&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;callback-carrier
&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"core show channels"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check for SIP authentication errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip set debug on"&lt;/span&gt;
&lt;span class="c"&gt;# Wait for callback to attempt&lt;/span&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/asterisk/messages | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"401&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;403&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;authentication"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Verify SIP credentials in &lt;code&gt;/etc/asterisk/sip-vicidial.conf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;asterisk &lt;span class="nt"&gt;-rx&lt;/span&gt; &lt;span class="s2"&gt;"sip show peer callback-carrier"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test SIP connectivity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sipsak &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; sip:YOUR_CARRIER_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 4: Callback Phone Numbers Formatted Incorrectly
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Callbacks dial incomplete or malformed numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check stored phone numbers in database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;callback_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone_number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;len&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt; 
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'PENDING'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PROCESSING'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Standardize phone number formatting in the schedule_callback.php script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;format_phone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$digits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;preg_replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/[^0-9]/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$phone&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$digits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nv"&gt;$digits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Add country code if missing&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$digits&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;$callback_phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;format_phone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$_POST&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'callback_phone'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 5: High Database Query Load from Callback Processing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Database CPU spikes every minute during callback processor runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check query execution time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; vicidial &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"SET GLOBAL log_queries_not_using_indexes=ON; SET GLOBAL long_query_time=1;"&lt;/span&gt;
&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/mysql/slow.log | &lt;span class="nb"&gt;grep &lt;/span&gt;callback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add batch processing and limit queries in the Perl script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight perl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Modify the query in vicidial_callbacks_processor.pl to add LIMIT&lt;/span&gt;
&lt;span class="nv"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;  &lt;span class="c1"&gt;# Process max 50 callbacks per run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Increase cron interval from 1 minute to 5 minutes if needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*/5 * * * * /usr/share/astguiclient/vicidial_callbacks_processor.pl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Issue 6: Agents Report Callback Button Not Working
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Agent screen loads but callback scheduling fails silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Diagnosis:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Check browser console for JavaScript errors:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open agent screen in browser&lt;/li&gt;
&lt;li&gt;Press F12 (Developer Tools)&lt;/li&gt;
&lt;li&gt;Go to Console tab&lt;/li&gt;
&lt;li&gt;Attempt to schedule callback&lt;/li&gt;
&lt;li&gt;Look for POST request failures&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Check PHP error logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/apache2/error.log | &lt;span class="nb"&gt;grep &lt;/span&gt;schedule_callback
&lt;span class="nb"&gt;sudo grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"php error&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;warning"&lt;/span&gt; /var/log/apache2/error.log | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Verify schedule_callback.php exists and has correct permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /var/www/html/agc/schedule_callback.php
&lt;span class="nb"&gt;sudo chown &lt;/span&gt;www-data:www-data /var/www/html/agc/schedule_callback.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check if session is properly initialized in agent screen code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"session_start"&lt;/span&gt; /var/www/html/agc/vicidial.php | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Section 9: Monitoring and Reporting
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 9.1: Callback Success Rate Report
&lt;/h3&gt;

&lt;p&gt;Generate a callback success report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;callback_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;total_callbacks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ANSWERED'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;answered&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'FAILED'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'PENDING'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'ANSWERED'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;success_rate_pct&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;callback_time&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="nb"&gt;DATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback_time&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;callback_date&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 9.2: Agent Callback Activity
&lt;/h3&gt;

&lt;p&gt;Track agent callback scheduling activity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;campaign_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;callbacks_scheduled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TIMESTAMPDIFF&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;MINUTE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;avg_lead_time_minutes&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;vicidial_callbacks&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;callback_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'AGENT'&lt;/span&gt;
&lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;created_date&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;DATE_SUB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;campaign_id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;callbacks_scheduled&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;You now have a complete, production-ready ViciDial scheduled callback system spanning both agent-initiated and campaign-level callbacks. Here's what you've implemented:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Components:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Database schema&lt;/strong&gt; with callback tracking tables and indexes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Campaign configuration&lt;/strong&gt; for automatic callback rules and retry logic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent screen integration&lt;/strong&gt; with PHP form handler for manual callback scheduling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Asterisk dialplan&lt;/strong&gt; with callback-specific contexts and SIP routing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated processing&lt;/strong&gt; via Perl script and cron job for periodic callback attempts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring and logging&lt;/strong&gt; for tracking callback performance and troubleshooting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best Practices Applied:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database indexing on frequently queried fields&lt;/li&gt;
&lt;li&gt;Rate limiting to prevent overwhelming carriers&lt;/li&gt;
&lt;li&gt;Time zone awareness for accurate callback scheduling&lt;/li&gt;
&lt;li&gt;Comprehensive error handling and retry logic&lt;/li&gt;
&lt;li&gt;Separation of agent and campaign callback workflows&lt;/li&gt;
&lt;li&gt;Detailed logging for audit trails and debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Next Steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Deploy in a test environment first&lt;/li&gt;
&lt;li&gt;Validate all database queries execute without errors&lt;/li&gt;
&lt;li&gt;Monitor callback success rate for 1-2 weeks&lt;/li&gt;
&lt;li&gt;Adjust retry intervals and time windows based on results&lt;/li&gt;
&lt;li&gt;Train agents on callback scheduling via &lt;code&gt;/agc/vicidial.php&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Set up monitoring alerts for callback processor failures&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Use the troubleshooting section to resolve environment-specific issues. Monitor &lt;code&gt;/var/log/asterisk/vicidial_callbacks.log&lt;/code&gt; and Asterisk CLI output regularly to ensure callbacks are processing as expected.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>sip</category>
      <category>voip</category>
    </item>
  </channel>
</rss>
