Skip to content

FreePBX

For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.

Using FreePBX and taking advantage of SignalWire’s disruptive pricing on DIDs and voice minutes is almost too easy.

Creating a SIP Endpoint

If you don’t have a SIP endpoint set up already, the first step is to create a SIP Endpoint via the SignalWire Dashboard and connect it to a phone number.

Editing the Signalwire SIP Endpoint for FreePBX

There are a couple of settings we need to change on the SIP Endpoint we have created in SignalWire to get good interworking with FreePBX:

  1. Set Encryption to ‘Required’
  2. Disable the Encryption option for AEAD_AES_256_GCM_8 as Asterisk is not currently supported and will cause a warning on the Asterisk Command Line (though calls would still work).

Configuring Asterisk

Add a SIP (PJSIP) trunk, and on the General tab enter the following:

Trunk Name: SignalWire (Can be anything you want)

Outbound CallerID: 1-469-FREEPBX (the DID you purchased)

Tab of an Edit Trunk page in the Asterisk interface. Hide CallerID is set to No. Outbound CallerID is set to the purchased DID, in this case 14693733729. CID options is set to Allow Any CID. Maximum Channels is blank. Asterisk Trunk Dial Options is set to System. Continue if Busy, Disable Trunk, and Monitor Trunk Failures are all set to No

Then, on the PJSIP Settings tab and the General sub-tab, enter the following:

Username: freepbx (the name you gave the SIP endpoint)

Secret: (the password you gave the SIP endpoint)

SIP Server: YOURSPACE-123456abc.sip.signalwire.com (your Space URI)

SIP Server Port: 5060 (standard SIP port)

Context: from-signalwire (default is ‘from-pstn’, but you must change it)

the PJSIP Settings tab of the Edit Trunk page in Asterisk. User name is set to freepbx. Authentication is set to Outbound. Registration is set to Send. Language Code is set to Default. The SIP Server is set to the desired Space URI. SIP Server Port is set to 5060. Context is set to 'from-signalwire'. Transport is set to '0.0.0.0-udp'

As the last step on this screen, on the PJSIP Settings tab and the Advanced sub-tab, the following three settings:

From Domain: YOURSPACE-123456abc.sip.signalwire.com (your Space URI)

From User: freepbx (the name you gave the SIP endpoint)

Media Encryption: SRTP via in-SDP (select from drop-down)

The Advanced tab of the Edit Trunk page in Asterisk. From Domain is set to the desired Space URI. From User is set to freepbx, or the name selected for the SIP endpoint. Media Encryption is set to SRTP via in-SDP.

Now your trunk is all set up!

Setting up the Dial Plan

There is a small amount of dialplan script to add (which we will place in a context called “from-signalwire” - remember, we set this in the above steps), in order to extract the dialed number from the SIP Header, before passing the call to FreePBX for normal processing.

We do this by entering the following lines in a file called extensions_custom.conf (which may be empty) using the inbuilt config editor in the Admin menu:

extensions_custom.conf

[from-signalwire]
exten => s,1,Set(numb=${CUT(CUT(PJSIP_HEADER(read,To),@,1),:,2)})
same => n,Goto(from-pstn,${numb:1},1)

The editor is at Admin > Config Edit > extensions_custom.conf:

The Configuration File Editor. The file 'extensions_custom.conf' is selected, and the provided code has been entered in the conf file.

Now just set up your inbound routing (the DID will be in the format 12223334444) and your outbound routing as normal.

That’s all there is to it! You should now be able to make outbound calls over your SignalWire SIP trunk and accept incoming calls to the number you purchased.

Tab of an Edit Trunk page in the Asterisk interface. Hide CallerID is set to No. Outbound CallerID is set to the purchased DID, in this case 14693733729. CID options is set to Allow Any CID. Maximum Channels is blank. Asterisk Trunk Dial Options is set to System. Continue if Busy, Disable Trunk, and Monitor Trunk Failures are all set to No

the PJSIP Settings tab of the Edit Trunk page in Asterisk. User name is set to freepbx. Authentication is set to Outbound. Registration is set to Send. Language Code is set to Default. The SIP Server is set to the desired Space URI. SIP Server Port is set to 5060. Context is set to 'from-signalwire'. Transport is set to '0.0.0.0-udp'

The Advanced tab of the Edit Trunk page in Asterisk. From Domain is set to the desired Space URI. From User is set to freepbx, or the name selected for the SIP endpoint. Media Encryption is set to SRTP via in-SDP.

The Configuration File Editor. The file 'extensions_custom.conf' is selected, and the provided code has been entered in the conf file.


Adding AI capabilities to FreeSWITCH

For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.

Did you know you can add an AI agent to your FreeSWITCH instance to handle your calls? In this guide, we’ll walk through an example code snippet for bridging a FreeSWITCH dialplan to a SignalWire AI Agent. This will allow an AI agent to take over the call as a virtual assistant or an IVR replacement, which is especially useful if you’re receiving many calls at once.

What is the SignalWire AI Agent?

SignalWire’s AI Agent is a low-code voice agent you can build using plain-text instructions. Personality, skills, and areas of expertise can all be customized in a simple drag-and-drop interface. You can also code your own if you want it to perform more complex functions.

You can use an AI agent as a virtual receptionist for your business, or as your own personal AI assistant. With the power of AI, the agent does not require pre-recorded messages or even text-to-speech prompts - it’s smart enough to engage in conversation with callers based on plain language instructions.


SIP Domain Application

Once you have created your AI agent, we can now assign the agent to a SIP domain application in your SignalWire dashboard. This will allow us to bridge the FreeSWITCH dialplan to the AI agent, so that the AI agent can take over the call.

SIP domain applications can be created from your space dashboard. Please follow our SIP domain applications introduction to follow a detailed guide on creating them.

Configuring the SIP Domain Application With Your AI Agent

Once you have created your domain application, you can now configure it to handle calls with your AI agent. In the Domain Apps page, click on the Edit button or the name for the domain application you want to configure.

In the Edit Domain App page, you can configure the domain application to handle incoming calls with your AI agent resource. Click on the Handle Using select box and choose an AI Agent. Then under the When a Call Comes In section, select the AI agent you want to use for the domain application.

For more complex AI use-cases, you might want to consider using SignalWire Markup Language (SWML). You can use a SWML script resource to define the AI agent’s behavior.

The Domain Applications page of the SignalWire dashboard with "Handle Incoming Calls" section selecting AI Agent

Assign an AI Agent to handle SIP domain application's incoming calls


FreeSWITCH Dialplan Configuration

Now that we have configured our AI agent in the SignalWire dashboard, we can now bridge the FreeSWITCH dialplan to the AI agent.

We’ll break down the components of the following code snippet:

<extension name="testing_ai" continue="false" uuid="6d0ecaef-9049-4920-8d1a-fc97ff4731f0">
	<condition field="destination_number" expression="^55555$">
		<action application="ring_ready" data=""/>
		<action application="answer" data=""/>
		<action application="sleep" data="500"/>
		<action application="set" data="hangup_after_bridge=true"/>
		<action application="set" data="ringback=local_stream://default"/>
		<action application="bridge" data="sofia/external/FreeSWITCH_AI@dev-freeswitch-ai.dapp.swire.io;transport=tcp"/>
	</condition>
</extension>

In the bridge, be sure to replace FreeSWITCH_AI@dev-freeswitch-ai.dapp.swire.io with your SignalWire domain app SIP address.

Dialplan Details

The given dialplan defines an extension within a FreeSWITCH configuration file. Let’s break down its components for a clearer understanding:

<extension>

<extension name="testing_ai" continue="false" uuid="6d0ecaef-9049-4920-8d1a-fc97ff4731f0">
  • name="testing_ai": The name of the extension. This is a unique identifier for the extension.
  • continue="false": Indicates whether FreeSWITCH should continue processing more extensions if this extension is executed. false means no further extensions will be processed.
  • uuid="6d0ecaef-9049-4920-8d1a-fc97ff4731f0": A unique identifier for this specific extension instance.

<condition>

<condition field="destination_number" expression="^55555$">
  • field="destination_number": This specifies that the condition is based on the destination number of the call.
  • expression="^55555$": A regular expression that matches the number 55555. Only calls to this exact number will trigger the actions defined in this extension.

<action>

Each <action> element represents a step in the call handling process. This configuration is particularly useful for setting up specialized call routing and handling behaviors in FreeSWITCH.

This block provides a detailed breakdown of the extension, condition, and actions within the provided FreeSWITCH dialplan configuration.

The actions are executed sequentially when the condition is met:

ring_ready

Sends a ringing signal to the caller, indicating that the call is being processed but not yet answered.

<action application="ring_ready" data=""/>
answer

Answers the call.

<action application="answer" data=""/>
sleep

Pauses the call processing for 500 milliseconds.

<action application="sleep" data="500"/>
Set hangup_after_bridge

Sets a variable hangup_after_bridge to true, which means the call will be hung up immediately after the bridge application completes its execution.

<action application="set" data="hangup_after_bridge=true"/>
Set ringback

Sets the ringback tone to be played to the caller. In this case, it uses a default local stream.

<action application="set" data="ringback=local_stream://default"/>
bridge

Bridges the call to the SignalWire SIP URI FreeSWITCH_AI@dev-freeswitch-ai.dapp.swire.io using the TCP transport. This effectively forwards the call to the specified SIP address

 <action application="bridge" data="sofia/external/FreeSWITCH_AI@dev-freeswitch-ai.dapp.swire.io;transport=tcp"/>

Installing FreeSWITCH or FreeSWITCH Enterprise

For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.

FreeSWITCH Terminology

Before we begin, here is some general FreeSWITCH terminology that may be important for understanding this article!

TermMeaning
FreeSWITCHThe publicly released version of FreeSWITCH for the community at large.
FreeSWITCH EnterpriseThe privately release version of FreeSWITCH for subscribed customers.
FSA generic term applicable to either FreeSWITCH or FreeSWITCH Enterprise
Production ServerA FreeSWITCH or FreeSWITCH Enterprise installation of packages from our stable repo.
Staging ServerA FreeSWITCH or FreeSWITCH Enterprise installation of packages from our unstable repo.
Lab ServerA FreeSWITCH or FreeSWITCH Enterprise installation by compilation of source code, build dependencies installed from the unstable repo.

Getting an Access Token

A SignalWire account is now required to download the pre-build FreeSWITCH binaries. First create a SignalWire Space, then go to the Personal Access Tokens section located on the left side of the Dashboard. From there, create a Personal Access Token. Click here to learn more.

The top right menu of the SignalWire Space

The top-right menu

The personal access token page on the SignalWire space

The top-right menu

In case you missed the previous emails on the matter, here is the recap of this change:

  • This specifically (and only) affects the built copy of the FreeSWITCH software - the binaries that turn Linux into FreeSWITCH instantly.
  • It does not impact actively running instances or change the process for accessing the open-source code, which will continue to be freely available on GitHub.
  • It may affect how you obtain FreeSWITCH dependencies or FreeSWITCH packages from our package repositories such as our Debian repository.
  • The changes do not affect our FreeSWITCH Enterprise method of authenticating.

Choosing a repository

Repositories are divided into publicly accessible repos for the community and privately accessible repos for FreeSWITCH Enterprise (FSE) subscribers. FSE repos receive code commits as soon as they are tested, while the public repo receives updates after further vetting for stability.

Each repo has release and unstable branches. Release branches are ideal for production environments, while unstable branches are suited for lab/testing/QA environments.

You will need a subscription and credentials to access the FreeSWITCH Enterprise repository.

Working with repos

Available to the community at large:

Click here to see more about installing or compiling the public version of FreeSWITCH.

apt-get update && apt-get install -y gnupg2 wget lsb-release
wget -O - https://files.freeswitch.org/repo/deb/debian-release/fsstretch-archive-keyring.asc | apt-key add -

echo "deb http://files.freeswitch.org/repo/deb/debian-release/ `lsb_release -sc` main" > /etc/apt/sources.list.d/freeswitch.list
echo "deb-src http://files.freeswitch.org/repo/deb/debian-release/ `lsb_release -sc` main" >> /etc/apt/sources.list.d/freeswitch.list

# you may want to populate /etc/freeswitch at this point.
<Badge type="tip" text="Fresh" />
# if /etc/freeswitch does not exist, the standard vanilla configuration is deployed
apt-get update && apt-get install -y freeswitch-meta-all

FreeSWITCH™ is now installed and can be accessed with

FreeSwitch CLI

fs_cli -rRS

Must be a subscriber to access FreeSWITCH Enterprise:

Click here to see more about installing or compiling the paid version of FreeSWITCH.

apt-get update && apt-get install -y gnupg2 wget lsb-release software-properties-common apt-transport-https
USERNAME=FSAUSER
PASSWORD=FSAPASS
wget --http-user=$USERNAME --http-password=$PASSWORD -O - https://fsa.freeswitch.com/repo/deb/fsa/pubkey.gpg | apt-key add -
echo "machine fsa.freeswitch.com login $USERNAME password $PASSWORD" > /etc/apt/auth.conf
echo "deb https://fsa.freeswitch.com/repo/deb/fsa/ `lsb_release -sc` 1.8" > /etc/apt/sources.list.d/freeswitch.list
echo "deb-src https://fsa.freeswitch.com/repo/deb/fsa/ `lsb_release -sc` 1.8" >> /etc/apt/sources.list.d/freeswitch.list

# you may want to populate /etc/freeswitch at this point.
# if /etc/freeswitch does not exist, the standard vanilla configuration is deployed
apt-get update && apt-get install -y freeswitch-meta-all

FreeSWITCH™ is now installed and can be accessed with

FreeSwitch CLI

fs_cli -rRS

If you need a compilation script, you can reach out to support@signalwire.com!


Clean and reconfigure with mod_signalwire

For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.

When using FreeSWITCH mod_signalwire, these commands can be used to unload mod_signalwire, discard current Connector configs, and obtain a new Token.

ActionCommand
Unload modfs_cli -x “unload mod_signalwire”
Kill gatewayfs_cli -x “sofia profile signalwire killgw signalwire”
Remove config filesfs_cli -x "signalwire token-reset"
Load modfs_cli-x “load mod_signalwire”
ReconfigureObtain new token with fs_cli -x "signalwire token", delete old connector from account Dashboard and complete setup process again; also re-forward Number to newly created Connector

Sending an SMS from FreeSWITCH XML dialplan

For AI agents: a documentation index is available at the root level at /llms.txt. Append /llms.txt to any URL for a page-level index, or .md for the markdown version of any page.

In order to send an SMS from a FreeSWITCH dialplan extension, we need to do a few things:

  1. Fill out the space_name, project_key, api_token, signalwire_number, and cellphone channel variables.

  2. Substitute all the spaces in the sms_body for the url encoded equivalent of %20:

sms_body=${system(printf 'aleg ${uuid} is calling bleg ${bleg_uuid}' | sed -r 's/ /%20/g')}

  1. Calculate the base64 equivalent of your authorization credentials so auth is properly setup for curl header append_headersAuthorization:'basic ${auth}':

auth=${system(printf '${project_key}:${api_token}' | base64 --wrap 0)

  1. Then, you have the Dialplan Extension. Dial 792 from any registered sip phone, or from bash do: fs_cli -x ‘originate loopback/792 echo inline’. Then, your cellphone should receive a text. Review the comments in the extension for additional notes.
<extension name="signalwire sms curl">
      <condition field="destination_number" expression="^792$">

        <!-- setup bleg uuid of this call so we we know it before hand -->
        <action application="set" inline="true" data="bleg_uuid=${create_uuid()}"/>

        <!-- setup signalwire credentials -->
        <action application="set" inline="true" data="space_name=XXXXXX"/>
        <action application="set" inline="true" data="project_key=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX"/>
        <action application="set" inline="true" data="api_token=PTXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"/>

        <!-- not using '+' here, just the 11 digit number itself, see curl line below -->
        <action application="set" inline="true" data="signalwire_number=1XXXXXXXXXX"/>
        <action application="set" inline="true" data="cellphone=1XXXXXXXXXX"/>

        <!-- urlencode your 'spaces' to '%20' so the text body will work in the url, may need to consider other untested puncuations -->
        <action application="set" inline="true" data="sms_body=${system(printf 'aleg ${uuid} is calling bleg ${bleg_uuid}' | sed -r 's/ /%20/g')}"/>

        <!-- We need to base64 encode the basic authorization -->
        <action application="set" inline="true" data="auth=${system(printf '${project_key}:${api_token}' | base64 --wrap 0)}"/>

        <!-- notice for E.164 formatting we encode the '+' to '%2B' for the 'From' and 'To' params on the URL -->
        <action application="curl" data="https://${space_name}.signalwire.com/api/laml/2010-04-01/Accounts/${project_key}/Messages.json?From=%2B${signalwire_number}&To=%2B${cellphone}&Body=${sms_body} append_headers Authorization:'basic ${auth}' post"/>

        <action application="hangup"/>
      </condition>
    </extension>

From this example, your cellphone should receive an SMS that looks like this:

​​​​​​​aleg 0739c067-c8b3-4a1d-bc8d-76f4a7e71c55 is calling bleg 426cd62b-fbcf-4fb2-a10a-9226dff5ac5d

SignalWire Developer Documentation