<?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>Forem: Alex Wilcox</title>
    <description>The latest articles on Forem by Alex Wilcox (@alxwlcx).</description>
    <link>https://forem.com/alxwlcx</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3697051%2F969f072a-616a-4d5f-92cc-bbc15edf73d0.png</url>
      <title>Forem: Alex Wilcox</title>
      <link>https://forem.com/alxwlcx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alxwlcx"/>
    <language>en</language>
    <item>
      <title>An open-source, HIPAA-eligible Twilio alternative</title>
      <dc:creator>Alex Wilcox</dc:creator>
      <pubDate>Tue, 06 Jan 2026 20:46:33 +0000</pubDate>
      <link>https://forem.com/alxwlcx/an-open-source-hipaa-eligible-twilio-alternative-33mf</link>
      <guid>https://forem.com/alxwlcx/an-open-source-hipaa-eligible-twilio-alternative-33mf</guid>
      <description>&lt;p&gt;GitHub repo: &lt;a href="https://github.com/VectorlyApp/open-telephony-stack" rel="noopener noreferrer"&gt;https://github.com/VectorlyApp/open-telephony-stack&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;For the most up-to-date setup instructions, please refer to&lt;/em&gt; &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/blob/main/README.md" rel="noopener noreferrer"&gt;open-telephony-stack/README.md&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Today, we're &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/blob/main/README.md" rel="noopener noreferrer"&gt;open-sourcing our telephony stack&lt;/a&gt;: production-grade, HIPAA-eligible &lt;a href="https://en.wikipedia.org/wiki/Voice_over_IP" rel="noopener noreferrer"&gt;VoIP&lt;/a&gt; infrastructure for AI voice agents. We've since moved on to other projects, but as AI voice agent companies keep multiplying, we think this fills a gap: there's no comprehensive, open-source, self-hostable option for this. Now there is. The only real cost is running the Dockerized servers.&lt;/p&gt;

&lt;p&gt;Here's why we built it. Last summer, we were building AI voice agents for healthcare practices. We needed to make and receive calls, stream audio in real-time, and stay HIPAA-eligible. Twilio seemed like the obvious choice, until we hit the paywall: &lt;strong&gt;$2,000/month&lt;/strong&gt; for HIPAA compliance before you've made a single call. (The package included things like longer audit log retention. Sorry, not impressed. That's &lt;a href="https://en.wikipedia.org/wiki/Monopoly_price" rel="noopener noreferrer"&gt;monopoly pricing&lt;/a&gt;.) For a startup, those figures can be prohibitive.&lt;/p&gt;

&lt;p&gt;So, we built our own stack: &lt;a href="https://github.com/asterisk/asterisk" rel="noopener noreferrer"&gt;Asterisk&lt;/a&gt; (an open-source &lt;a href="https://en.wikipedia.org/wiki/Business_telephone_system#Private_branch_exchange" rel="noopener noreferrer"&gt;PBX&lt;/a&gt;), &lt;a href="https://aws.amazon.com/chime/chime-sdk" rel="noopener noreferrer"&gt;AWS Chime SDK&lt;/a&gt; (for &lt;a href="https://en.wikipedia.org/wiki/Session_Initiation_Protocol" rel="noopener noreferrer"&gt;SIP&lt;/a&gt; trunking and phone numbers), and a &lt;a href="https://github.com/fastapi/fastapi" rel="noopener noreferrer"&gt;FastAPI&lt;/a&gt; shim that bridges old-school telephony to modern WebSocket APIs.&lt;/p&gt;

&lt;p&gt;Below, I'll walk through the architecture and setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is
&lt;/h2&gt;

&lt;p&gt;A complete and secure telephony system built to handle both inbound and outbound calls:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receives calls via &lt;a href="https://docs.aws.amazon.com/chime-sdk/latest/ag/voice-connectors.html" rel="noopener noreferrer"&gt;AWS Chime Voice Connector&lt;/a&gt; (you get a real phone number).&lt;/li&gt;
&lt;li&gt;Terminates SIP/TLS on Asterisk running in Docker.&lt;/li&gt;
&lt;li&gt;Bridges the audio via &lt;a href="https://en.wikipedia.org/wiki/Real-time_Transport_Protocol" rel="noopener noreferrer"&gt;RTP&lt;/a&gt; to a WebSocket connection.&lt;/li&gt;
&lt;li&gt;Streams base64 &lt;a href="https://en.wikipedia.org/wiki/Mu-law_algorithm" rel="noopener noreferrer"&gt;μ-law&lt;/a&gt; audio to your AI voice server.&lt;/li&gt;
&lt;li&gt;Twilio-like API (the WebSocket interface is modeled after Twilio's Media Streams API, so if you've built with Twilio before, you'll feel right at home).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You bring your own AI. This just handles the phone infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is for
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Use case examples:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building voice AI in healthcare and need HIPAA compliance without Twilio's BAA costs.&lt;/li&gt;
&lt;li&gt;Customizing call handling in ways Twilio doesn't allow.&lt;/li&gt;
&lt;li&gt;Wanting full control over your telephony stack.&lt;/li&gt;
&lt;li&gt;Learning how telephony infrastructure works and building a VoIP stack from scratch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Consider alternatives if:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You just need basic voice for a side project (Twilio is easier).&lt;/li&gt;
&lt;li&gt;You don't want to manage infrastructure.&lt;/li&gt;
&lt;li&gt;You don't have any special compliance needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Infrastructure requires time and maintenance. That's the trade-off.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Overview
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgxdzuti3r8jpt4v4i9t6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgxdzuti3r8jpt4v4i9t6.png" alt=" " width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Port reference
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Protocol&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;Asterisk SIP&lt;/td&gt;
&lt;td&gt;5061&lt;/td&gt;
&lt;td&gt;TCP/TLS&lt;/td&gt;
&lt;td&gt;SIP signaling with AWS Chime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asterisk ARI&lt;/td&gt;
&lt;td&gt;8088&lt;/td&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;Asterisk REST Interface (localhost only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shim server&lt;/td&gt;
&lt;td&gt;8080&lt;/td&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;FastAPI server, health endpoints&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RTP media&lt;/td&gt;
&lt;td&gt;10000-10299&lt;/td&gt;
&lt;td&gt;UDP&lt;/td&gt;
&lt;td&gt;Audio streams to/from Asterisk&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AWS Chime Voice Connector:&lt;/strong&gt; The &lt;a href="https://en.wikipedia.org/wiki/Public_switched_telephone_network" rel="noopener noreferrer"&gt;PSTN&lt;/a&gt; gateway. You provision a phone number here. Chime handles the carrier relationships, E911, etc. Calls arrive as SIP/TLS on port 5061.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asterisk PBX:&lt;/strong&gt; Open-source telephony server, Dockerized. Handles SIP signaling, RTP media, call routing. The key here is using &lt;a href="https://docs.asterisk.org/Configuration/Interfaces/Asterisk-REST-Interface-ARI/Getting-Started-with-ARI" rel="noopener noreferrer"&gt;ARI (Asterisk REST Interface)&lt;/a&gt; instead of traditional dialplan scripting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shim server:&lt;/strong&gt; A FastAPI application that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connects to Asterisk via ARI WebSocket&lt;/li&gt;
&lt;li&gt;Creates &lt;code&gt;ExternalMedia&lt;/code&gt; channels for RTP bridging (the "external media" here being your AI voice agent server)&lt;/li&gt;
&lt;li&gt;Maintains a perfect 20ms RTP cadence regardless of WebSocket jitter&lt;/li&gt;
&lt;li&gt;Forwards audio as base64 μ-law to your downstream voice server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Your AI voice server:&lt;/strong&gt; Whatever you're building. Receives WebSocket connection with Twilio-compatible media events. Could be &lt;a href="https://platform.openai.com/docs/guides/realtime" rel="noopener noreferrer"&gt;OpenAI Realtime&lt;/a&gt;, &lt;a href="https://aws.amazon.com/nova/models" rel="noopener noreferrer"&gt;AWS Nova Sonic&lt;/a&gt;, a custom ASR/TTS pipeline, whatever.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We've included a sample implementation: &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/blob/main/src/servers/voice_agent_server.py" rel="noopener noreferrer"&gt;open-telephony-stack/src/servers/voice_agent_server.py&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  DNS configuration
&lt;/h3&gt;

&lt;p&gt;Before setting up TLS certificates, you need to configure DNS so that AWS Chime can resolve your Asterisk server's hostname. Create an &lt;a href="https://en.wikipedia.org/wiki/List_of_DNS_record_types#Resource_records" rel="noopener noreferrer"&gt;&lt;code&gt;A&lt;/code&gt; record&lt;/a&gt; pointing your SIP subdomain to your EC2 instance's &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/elastic-ip-addresses-eip.html" rel="noopener noreferrer"&gt;Elastic IP&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Record type&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;TTL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sip.yourdomain.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your Elastic IP (e.g., &lt;code&gt;54.123.45.67&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;300 (or default)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This DNS record must be in place before:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Requesting Let's Encrypt certificates (Certbot validates domain ownership)&lt;/li&gt;
&lt;li&gt;Configuring AWS Chime Voice Connector termination (Chime needs to resolve the hostname)&lt;/li&gt;
&lt;li&gt;Setting &lt;code&gt;external_signaling_address&lt;/code&gt; in &lt;code&gt;pjsip.conf&lt;/code&gt; (must match the DNS name)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After creating the record, wait for DNS propagation (usually a few minutes, but can take up to 48 hours depending on TTL). You can verify with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig sip.yourdomain.com
&lt;span class="c"&gt;# or&lt;/span&gt;
nslookup sip.yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A note on TLS certificates
&lt;/h3&gt;

&lt;p&gt;AWS Chime requires TLS for SIP. This setup uses &lt;a href="https://en.wikipedia.org/wiki/Let%27s_Encrypt" rel="noopener noreferrer"&gt;Let's Encrypt&lt;/a&gt; to meet that requirement:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/certbot/certbot" rel="noopener noreferrer"&gt;Certbot&lt;/a&gt; runs on the EC2 instance, bound to port 80.&lt;/li&gt;
&lt;li&gt;Certificates are issued for your SIP domain (e.g., &lt;code&gt;sip.yourdomain.com&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Asterisk reads certs from &lt;code&gt;/etc/letsencrypt/live/...&lt;/code&gt; via Docker volume mount.&lt;/li&gt;
&lt;li&gt;A renewal hook reloads Asterisk when certs rotate.&lt;/li&gt;
&lt;li&gt;Chime validates the cert against Let's Encrypt's CA root.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This means no self-signed certs, no manual renewal, no cert expiration surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Call flow
&lt;/h2&gt;

&lt;p&gt;Here's what happens when someone calls your number:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Caller dials your AWS Chime phone number&lt;/li&gt;
&lt;li&gt;Chime sends SIP INVITE to your Asterisk server (TLS:5061)&lt;/li&gt;
&lt;li&gt;Asterisk matches the call in &lt;code&gt;extensions.conf&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Answer()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Stasis(voice-agent)&lt;/code&gt;

&lt;ol&gt;
&lt;li&gt;ARI sends &lt;code&gt;StasisStart&lt;/code&gt; event to shim server via WebSocket&lt;/li&gt;
&lt;li&gt;Shim server:
a. Opens WebSocket to your voice server
b. Creates ARI mixing bridge
c. Adds PSTN channel to bridge
d. Allocates UDP port for RTP (10000-10299); each live call gets its own port
e. Creates &lt;code&gt;ExternalMedia&lt;/code&gt; channel pointing to that port
f. Adds &lt;code&gt;ExternalMedia&lt;/code&gt; channel to bridge&lt;/li&gt;
&lt;li&gt;Audio flows: PSTN ↔ Bridge ↔ &lt;code&gt;ExternalMedia&lt;/code&gt; ↔ Shim (RTP) ↔ Voice Server (WSS)&lt;/li&gt;
&lt;li&gt;Caller hangs up (or AI ends call via a &lt;a href="https://en.wikipedia.org/wiki/Large_language_model#Tool_use" rel="noopener noreferrer"&gt;tool call&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;ARI sends &lt;code&gt;ChannelHangupRequest&lt;/code&gt; / &lt;code&gt;ChannelDestroyed&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Shim cleans up: closes WebSocket, deletes bridge, releases port&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Asterisk server configuration
&lt;/h2&gt;

&lt;p&gt;These config files live in &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/tree/main/deployment/asterisk-server/asterisk-config" rel="noopener noreferrer"&gt;&lt;code&gt;deployment/asterisk-server/asterisk-config/&lt;/code&gt;&lt;/a&gt;. The Docker container mounts this directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/tree/main/deployment/asterisk-server/asterisk-config/pjsip.conf" rel="noopener noreferrer"&gt;&lt;code&gt;pjsip.conf&lt;/code&gt;&lt;/a&gt;: SIP trunk configuration
&lt;/h3&gt;

&lt;p&gt;This is the most important file. It configures the &lt;a href="https://en.wikipedia.org/wiki/SIP_trunking" rel="noopener noreferrer"&gt;SIP trunk&lt;/a&gt; to AWS Chime, including transport settings, TLS certificates, inbound/outbound endpoints, and where to route calls.&lt;/p&gt;

&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;external_signaling_address&lt;/code&gt; must match your DNS and certificate&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;local_net&lt;/code&gt; tells Asterisk what's "inside" vs "outside" for NAT handling&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;verify_server=no&lt;/code&gt; because Chime doesn't send a client cert we need to validate&lt;/li&gt;
&lt;li&gt;The cert/key files are what Asterisk presents to Chime during TLS handshake&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/tree/main/deployment/asterisk-server/asterisk-config/extensions.conf" rel="noopener noreferrer"&gt;&lt;code&gt;extensions.conf&lt;/code&gt;&lt;/a&gt;: Dialplan
&lt;/h3&gt;

&lt;p&gt;Defines what happens when calls arrive or are placed. This is a minimal dialplan; everything interesting happens in the &lt;code&gt;Stasis&lt;/code&gt; application.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Stasis(voice-agent)&lt;/code&gt; line is the magic. It moves the call from traditional dialplan processing into ARI, where our shim server takes over.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/tree/main/deployment/asterisk-server/asterisk-config/ari.conf" rel="noopener noreferrer"&gt;&lt;code&gt;ari.conf&lt;/code&gt;&lt;/a&gt;: REST API access
&lt;/h3&gt;

&lt;p&gt;Configures the Asterisk REST Interface credentials. The shim server uses these to connect to ARI.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/tree/main/deployment/asterisk-server/asterisk-config/http.conf" rel="noopener noreferrer"&gt;&lt;code&gt;http.conf&lt;/code&gt;&lt;/a&gt;: HTTP server for ARI
&lt;/h3&gt;

&lt;p&gt;Configures Asterisk's built-in HTTP server, which hosts the ARI endpoints. Bound to localhost only for security; the shim server runs on the same machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/tree/main/deployment/asterisk-server/asterisk-config/rtp.conf" rel="noopener noreferrer"&gt;&lt;code&gt;rtp.conf&lt;/code&gt;&lt;/a&gt;: RTP port range
&lt;/h3&gt;

&lt;p&gt;Defines the UDP port range for RTP media streams. Each call uses one port from this range (10000 - 10299).&lt;/p&gt;

&lt;p&gt;300 ports = 300 concurrent calls max. Adjust based on your needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/tree/main/deployment/asterisk-server/asterisk-config/modules.conf" rel="noopener noreferrer"&gt;&lt;code&gt;modules.conf&lt;/code&gt;&lt;/a&gt;: Loaded modules
&lt;/h3&gt;

&lt;p&gt;Specifies which Asterisk modules to load at startup. We explicitly load only what we need: PJSIP for SIP, ARI for programmatic control, and the μ-law codec for audio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup guide
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prereqs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS account&lt;/li&gt;
&lt;li&gt;EC2 instance (recommended &lt;code&gt;t3.medium&lt;/code&gt; minimum, Amazon Linux 2023)&lt;/li&gt;
&lt;li&gt;Elastic IP (attached to the EC2 VM; Chime Voice Connectors require static IP addresses)&lt;/li&gt;
&lt;li&gt;Domain name with DNS pointing to the Elastic IP&lt;/li&gt;
&lt;li&gt;Docker and Docker Compose&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Provision AWS Chime Voice Connector
&lt;/h3&gt;

&lt;p&gt;a. Go to AWS Chime SDK console&lt;br&gt;
b. Create a Voice Connector&lt;br&gt;
c. Under "Phone numbers," claim or port a number&lt;br&gt;
d. Under "Termination," add your Asterisk server's domain and IP&lt;br&gt;
e. Under "Origination," configure where to send inbound calls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Host:&lt;/strong&gt; &lt;code&gt;sip.yourdomain.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port:&lt;/strong&gt; &lt;code&gt;5061&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocol:&lt;/strong&gt; &lt;code&gt;TLS&lt;/code&gt;
f. Note your Voice Connector hostname (for &lt;code&gt;pjsip.conf&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. Set up TLS certificates
&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;# install certbot&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;yum &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; certbot

&lt;span class="c"&gt;# get certificate (port 80 must be open)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;certbot certonly &lt;span class="nt"&gt;--standalone&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--preferred-challenges&lt;/span&gt; http &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; sip.yourdomain.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--agree-tos&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; your@email.com

&lt;span class="c"&gt;# enable auto-renewal&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; certbot-renew.timer

&lt;span class="c"&gt;# create renewal hook to reload Asterisk&lt;/span&gt;
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /etc/letsencrypt/renewal-hooks/deploy
&lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/letsencrypt/renewal-hooks/deploy/reload-asterisk.sh &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null &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;'
#!/bin/sh
docker exec asterisk-server asterisk -rx "core reload"
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;sudo chmod&lt;/span&gt; +x /etc/letsencrypt/renewal-hooks/deploy/reload-asterisk.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Configure security groups
&lt;/h3&gt;

&lt;p&gt;Allow inbound traffic only from AWS Chime IPs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Source&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;22&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;Your IP&lt;/td&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;0.0.0.0/0&lt;/td&gt;
&lt;td&gt;Let's Encrypt ACME challenge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5061&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;AWS Chime IPs&lt;/td&gt;
&lt;td&gt;SIP/TLS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10000-10299&lt;/td&gt;
&lt;td&gt;UDP&lt;/td&gt;
&lt;td&gt;AWS Chime IPs&lt;/td&gt;
&lt;td&gt;RTP media&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The repo includes &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/blob/main/aws_lambda/update_telephony_vm_sg.py" rel="noopener noreferrer"&gt;a Lambda function&lt;/a&gt; that automatically updates your security group when &lt;a href="https://docs.aws.amazon.com/vpc/latest/userguide/aws-ip-ranges.html" rel="noopener noreferrer"&gt;AWS publishes new IP ranges&lt;/a&gt;. Specifically, this is for their &lt;code&gt;AMAZON&lt;/code&gt;, &lt;code&gt;EC2&lt;/code&gt;, and &lt;code&gt;CHIME_VOICECONNECTOR&lt;/code&gt; services.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Deploy Asterisk server
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/blob/main/deployment/asterisk-server/docker-compose.yml" rel="noopener noreferrer"&gt;Docker compose&lt;/a&gt; here uses the &lt;code&gt;mlan/asterisk&lt;/code&gt; Dockerized Asterisk image, which is one of the dockerized versions of Asterisk. For additional customizability (e.g., specific module selection, custom build flags, or different base images), you can build your own Docker image using the &lt;a href="https://github.com/asterisk/asterisk" rel="noopener noreferrer"&gt;Asterisk GitHub repository&lt;/a&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;cd &lt;/span&gt;deployment/asterisk-server

&lt;span class="c"&gt;# edit config files&lt;/span&gt;
&lt;span class="c"&gt;# - pjsip.conf: Update domain, cert paths, Chime voice connector host&lt;/span&gt;
&lt;span class="c"&gt;# - ari.conf: Set a secure password&lt;/span&gt;
&lt;span class="c"&gt;# - rtp.conf: Adjust port range if needed&lt;/span&gt;

&lt;span class="c"&gt;# start Asterisk server&lt;/span&gt;
docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;

&lt;span class="c"&gt;# check logs&lt;/span&gt;
docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; asterisk-server

&lt;span class="c"&gt;# access CLI&lt;/span&gt;
docker &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; asterisk-server asterisk &lt;span class="nt"&gt;-rvvvvv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Deploy shim server
&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;# create .env file&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .env &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;'
ARI_BASE=http://127.0.0.1:8088/ari
ARI_USER=ariuser
ARI_PASS=your-secure-password-here
ARI_APP=voice-agent
EXTERNAL_MEDIA_HOST=127.0.0.1
ECS_MEDIA_WSS_URL=wss://your-voice-server.internal/voice/voice
RTP_PORT_START=10000
RTP_PORT_END=10299
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="c"&gt;# build and run&lt;/span&gt;
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; asterisk-shim &lt;span class="nt"&gt;-f&lt;/span&gt; deployment/shim-server/Dockerfile &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--env-file&lt;/span&gt; .env &lt;span class="nt"&gt;--network&lt;/span&gt; host &lt;span class="nt"&gt;--name&lt;/span&gt; asterisk-shim asterisk-shim

&lt;span class="c"&gt;# check shim server health&lt;/span&gt;
curl http://localhost:8080/health
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Test Asterisk server
&lt;/h3&gt;

&lt;p&gt;Call your AWS Chime phone number. Watch the 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="c"&gt;# Asterisk logs (SIP/RTP activity)&lt;/span&gt;
docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; asterisk-server

&lt;span class="c"&gt;# shim server logs (session lifecycle)&lt;/span&gt;
docker logs &lt;span class="nt"&gt;-f&lt;/span&gt; asterisk-shim
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
a. SIP &lt;a href="https://en.wikipedia.org/wiki/Session_Initiation_Protocol#Requests" rel="noopener noreferrer"&gt;INVITE&lt;/a&gt; received&lt;br&gt;
b. &lt;code&gt;CallSession&lt;/code&gt; created&lt;br&gt;
c. &lt;code&gt;ExternalMedia&lt;/code&gt; channel established&lt;br&gt;
d. RTP flowing&lt;br&gt;
e. WebSocket connection to your voice server&lt;/p&gt;
&lt;h3&gt;
  
  
  7. Implement your AI voice server
&lt;/h3&gt;

&lt;p&gt;The WebSocket API is modeled after Twilio's Media Streams. If you've integrated with Twilio before, this will look familiar: same event structure, same audio format.&lt;/p&gt;

&lt;p&gt;We've included a sample implementation: &lt;a href="https://github.com/VectorlyApp/open-telephony-stack/blob/main/src/servers/voice_agent_server.py" rel="noopener noreferrer"&gt;&lt;code&gt;voice_agent_server.py&lt;/code&gt;&lt;/a&gt;. This demonstrates how to handle the WebSocket events and process audio in real-time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audio format specs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Format:&lt;/strong&gt; μ-law (PCMU)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sample rate:&lt;/strong&gt; 8000 Hz&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frame size:&lt;/strong&gt; 160 bytes (20ms)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encoding:&lt;/strong&gt; Base64&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key WebSocket events:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start event&lt;/strong&gt; (shim → voice server):&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"streamSid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unique-stream-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"callSid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asterisk-channel-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"customParameters"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"asterisk-shim"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ulaw"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Media event&lt;/strong&gt; (bidirectional):&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"streamSid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unique-stream-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"media"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"base64-encoded-ulaw-audio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1234&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Clear event&lt;/strong&gt; (voice server → shim) – clears the audio buffer immediately for barge-in / interruption handling:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"clear"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Mark event&lt;/strong&gt; (bidirectional) – used for tracking audio playback position:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mark"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"streamSid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unique-stream-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mark"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"responsePart"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stop event&lt;/strong&gt; (either direction) – ends the call:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"streamSid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unique-stream-id"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Asterisk
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.asterisk.org/" rel="noopener noreferrer"&gt;Asterisk official site&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AWS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/chime-sdk" rel="noopener noreferrer"&gt;AWS Chime SDK documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/chime-sdk/latest/ag/voice-connectors.html" rel="noopener noreferrer"&gt;AWS Chime Voice Connectors&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GitHub
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/asterisk/asterisk" rel="noopener noreferrer"&gt;Asterisk GitHub repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/fastapi/fastapi" rel="noopener noreferrer"&gt;FastAPI GitHub repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/VectorlyApp/open-telephony-stack" rel="noopener noreferrer"&gt;Vectorly Open Telephony Stack GitHub repo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Docker Hub
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://hub.docker.com/r/mlan/asterisk/tags" rel="noopener noreferrer"&gt;mlan/asterisk Docker image&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Wikipedia
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/List_of_DNS_record_types" rel="noopener noreferrer"&gt;List of DNS record types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Business_telephone_system#Private_branch_exchange" rel="noopener noreferrer"&gt;Private Branch Exchange (PBX)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Public_switched_telephone_network" rel="noopener noreferrer"&gt;Public Switched Telephone Network (PSTN)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Real-time_Transport_Protocol" rel="noopener noreferrer"&gt;Real-time Transport Protocol (RTP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Session_Initiation_Protocol" rel="noopener noreferrer"&gt;Session Initiation Protocol (SIP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/SIP_trunking" rel="noopener noreferrer"&gt;SIP trunking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Voice_over_IP" rel="noopener noreferrer"&gt;Voice over Internet Protocol (VoIP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Mu-law_algorithm" rel="noopener noreferrer"&gt;μ-law Algorithm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>voiceai</category>
      <category>opensource</category>
      <category>hipaa</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
