← Go Back  ||  NPSTN Home

Version 1.7.14 — (last revised 2021/06/02)

© 2024 Northern Pacific Switched Telecommunications Network

NPSTN. Bringing the past into the future.


  1. Dedication
  2. Introduction
    1. History
  3. Requirements
    1. Hardware
    2. Software
  4. Member Compact & Rules
  5. Getting Started
    1. Pre-Requisites
    2. Installing Asterisk
    3. Reserving an Office Code
    4. User Control Panel
    5. Boilerplate Code
    6. Encryption
      1. RSA Encryption
    7. Initial Configuration
      1. asterisk.conf
      2. modules.conf
      3. sip.conf
      4. ATA Digit Map
      5. iax.conf
      6. extensions.conf
    8. Mail
  6. Answer Supervision
  7. Common Contexts
    1. Incoming Route
    2. Basic Contexts
    3. Global Variables
    4. Billing Subroutine
      1. v2
      2. v1
    5. Music on Hold
    6. City Dialtone DISA
      1. Macro
      2. GoTo Contexts
    7. Dial NPSTN
      1. Original Macro
      2. Subroutine
      3. Macro for Older Asterisk Systems
    8. Verification Subroutines
      1. [dtnpstn] patch
    9. IP Check
    10. NetCID
    11. Tie Lines (and FX lines)
  8. Inward Routings
  9. Billing
    1. NPSTN Mobile Telephone Service
    2. ZEnith Numbers
  10. eXtensible Dialplan
    1. Number Aliases
  11. Standards
    1. Standard Variables
    2. NPSTN Intercept Codes
    3. ANI II Digits
    4. Numbering Plan
      1. #5XB Automatic Call Distributor Hack
      2. Case Study: NPSTNNA01
      3. Standard Numbers
  12. Exchanges
    1. PSTN DID Exchanges
      1. Allocation
  13. Vintage Add-Ons
    1. Pat Fleet Asterisk Sounds
    2. Automatic Intercept System
    3. ANAC
    4. Speaking Clock
    5. Airport Weather
    6. Millwatt Test Tone
    7. Ringback
    8. Revertive Pulsing Script
    9. MFer
    10. SFer
    11. Dial Pulser
    12. Conference Bridges
    13. Non-Supervising Conference Bridges
    14. Answering Machines
    15. Hook Flashing
    16. Announcement Drums
    17. Crosstalk
    18. Echo Test
    19. Silent Termination
    20. Live Feeds
    21. Loop Arounds
    22. ChanSpy Verification
    23. T1 Trunks
    24. Modems
    25. Fax
    26. MF/ACTS Detectors
    27. NPSTN Cadence Plan
    28. Multiple Level Precedence and Preemption
    29. Manual Service
    30. Vertical Service Codes
  14. Payphone Trunks
  15. Project MF 2.0
  16. StepNet
    1. Background
    2. Discussion
      1. Discussion #1
      2. Discussion #2
      3. Discussion #3
    3. Proposal
      1. Draft 1: Retired Conception
      2. Draft 2: Current Conception
    4. Sounds
    5. Dialplan Code
  17. Automatic Operators
  18. Services
    1. CNAM
    2. Paging
    3. Telegrams
    4. Wake Up Calls
    5. Toll-Free Number
    6. SIT-COM
  19. Blue Box
  20. NPSTN System Practices
  21. C*NET Connectivity
  22. PSTN Connectivity
    1. Incoming Calls
      1. IPComms
        1. Registration
        2. Required Contexts
      2. CallCentric
        1. Registration
        2. Required Contexts
    2. Outgoing Calls
      1. Skyetel
        1. NerdVittles Promotion
        2. Skyetel Configuration
        3. Required Contexts
      2. Google Voice
        1. PJSIP Dependencies
        2. Google Voice Installation
        3. Required Contexts
  23. Further Add-Ons
    1. [public] context
    2. features.conf
    3. Extending Your System: Shell Scripts
      1. Windows & Unix Encoding
    4. Speech Recognition: IBM Watson
    5. Evan Doorbell
  24. Appendix A: Helpful Supplemental Tools
    1. Using SoX to convert audio files
      1. Linux
      2. Windows
    2. Using iptables manually


This documentation is dedicated to the late Brian Clancy, who sadly passed away of terminal illnesses on 2020/02/13. Without Brian, not only would this documentation not exist, this network would not exist. Brian was a good friend to all and a kind and patient mentor from the beginning of NPSTN. Brian lives on as the voice of NPSTN, reachable on network operator numbers, Coin Zone calls, TIME (231-2301), and as a conversation partner (549-3344). But his most important impact has been his influence on the countless individuals whom he has left behind. He will be forever remembered for his persistence, work ethic, and strong-spirited determination. Thank you, Brian, for all you have done!


Given the rapidity with which NPSTN (Northern Pacific Switched Telecommunications Network, also NoveltyPSTN or NostalgicPSTN) grew in its infancy, it was determined that there existed a great need to document all of its common dialplan code, dependencies, and instructions in order to make it easier for both its operators and any newbies joining the network to find their way. This reference is a continually-growing repository of common code and resources shared across NPSTN nodes that experienced node operators and newcomers alike will find helpful. This documentation consists of "sub-docs" which individually provide detailed and useful information about different components of the network and the nodes that comprise it.

This reference also provides a set of standards to which node operators are encouraged to adhere. Doing so will result in the best possible experience across the network for everyone.

The basis for this documentation was created by D. Cruz. This webpage — and the documentation itself — were largely assembled by N. Albert. The inspiration for this documentation stems from Brian Clancy.


Created and founded by Dylan Cruz, NPSTN rolled into its beta phase in August 2018 but quickly matured after that. By the beginning of 2019, NPSTN lay claim to approximately a dozen different nodes in several different countries (including the US, the UK, Canada, Australia, and Norway). Conceived as an alternative and supplement to C*NET, NPSTN today is a fully-fledged VoIP telephone network for phone phreaks and phone collectors.

To understand in a nutshell what makes NPSTN unique, here are a few major differences between NPSTN and C*NET:

  • C*NET uses ENUM lookups whereas NPSTN uses custom APIs
  • NPSTN has a more "phreak-friendly" culture that is more tolerant of phreaking activities, i.e. tandem stacking
  • Far more DISAs exist on NPSTN and nearly all use city dialtones
  • Nearly 100% of the network sounds on NPSTN are pre-Precise Tone Plan sounds
  • While C*NET uses country codes, NPSTN uses only conventional 7-digit numbers
  • NPSTN features special numbers like 0, N11, POPCORN, etc.

Otherwise, the two networks are both very similar. Both are home to phreaks and collectors, Asterisk boxes, and electromechanical switches. Both use IAX trunking and both have redundant lookup systems. Many node operators are on both networks, and gateways exist between the two networks, as well as from the PSTN.

Though Cruz remains at the head of development on NPSTN, particularly the backend, there are other large contributors to the network as well. Albert has spearheaded improvements to webpages and documentation and has been responsible for the creation of a large amount of Asterisk dialplan code part of the network today, including centralized and automatic operator services. He is also the main developer on the BBS and backend terminal systems, including those used by NPSTN operators. Much thanks to Brian Clancy, a longtime C*NET node owner, who taught Cruz and Albert much of what each knew about Asterisk, encouraging the two to write clean, proper, well-documented Asterisk code. You could say he serves as a sort of experiential consultant. Brian has been a major help and resource to both throughout the entire process and NPSTN as it is would not exist without his help, advice, and Asterisk wisdom and expertise. All three have been a part of NPSTN almost since the beginning and constitute its "core development team", bouncing ideas off of each other (although this also takes place on the public NPSTN listserv), discussing network standards, and testing dialplan code.

In its current phase, the next major addition to NPSTN is StepNet, which will (to a limited extent) simulate PSTN tandem switching back in the day. Calls will be routed through NPSTN nodes in a geographically realistic way until they reach their destination. StepNet is a layer on top of NPSTN, and is an experience that can be toggled on or off. StepNet is currently in development, but you can peruse its related documentation here and keep up with the project as progress is made.

Update: StepNet in its first phase is already reality! You can make StepNet calls over NPSTN. The next phase in the project is completing StepNet calls through electromechanical switches, which will require more complicated routing.



To operate your own NPSTN node, you will need a server running Asterisk, a powerful but free VoIP/TDM/analog PBX/CO switch software solution. You don't need a powerful system to run Asterisk — many node operators on C*NET and NPSTN use servers with 1GB of RAM or less — even 512MB will be sufficient. uLaw (pronounced mu-law or, frequently but erroneously, u-law), is the preferred codec in the United States for VoIP, as it is 64kbps, or PSTN quality. Consequently, you will also need an Internet connection (but you knew that already, didn't you?). Although you can operate a node even with a very low-speed Internet connection, it is undesirable to have a connection speed of under 512kbps. A T1 (1.544Mbps) will be sufficient, and any package of broadband Internet will be more than enough bandwidth for several VoIP calls.

Some of the core servers on NPSTN are hosted servers, meaning they are not physically located at the premises of their owners. Hosted servers can be a great option for those just getting started or those without decent Internet connections or the space and resources to operate an on-premises Asterisk server. If you go this route, do understand its limitations. First, we can't stress enough how important it is to choose a reputable host. Initially, the main nodes on NPSTN were hosted by HiFormance on a plan that was (wait for it!) $10/year. Of course, you know what they say: if it sounds too good to be true, it probably is. In mid-December 2018, without warning, HiFormance servers stopped working without warning — owners were unable to SSH into the servers and the services on them went down. With some effort, it was discovered that HiFormance was closing its doors. Ironically, a post on the website informed its customers that HiFormance would be closing its doors and that data should be backed up, yet, HiFormance never emailed its customers and most people discovered this only after their servers stopped working. Fortunately, all of the NPSTN servers had been fully backed up and were soon operational with a new host.

Operating your Asterisk server locally is also an option. Almost any old computer from this millennium will suffice, but your options are really quite flexible. Asterisk can run seamlessly on a Raspberry Pi, which is a compact, low-energy solution that works well for many node operators. While you may run into roadblocks with connecting channel banks and other equipment to a Pi, for those with just ATAs (analog telephone adapters), a Pi should be more than sufficient. Many NPSTN nodes are operated using Raspberry Pis.

From our experiences with multiple VPS providers, we've seen some of the things that work and don't work well. We recommend using Digital Ocean's $5 per month Droplet, which is very affordable and reliable. You can sign up using our referral link if you aren't already a member, and we'll both get some free credit! Thanks for keeping our costs low, so we can work on more cool projects for you!


Asterisk PBX is a free open-source VoIP PBX solution that has "taken the telecom industry by storm". It is used by both C*NET and NPSTN and is a requirement for operating a node, no matter what hardware is being used.

To start with, you'll need a flavor of Linux (also free and open-source) in order to run Asterisk. We recommend Debian, which works well on a variety of platforms. Any Linux commands featured in this documentation will assume an instance of Debian, but if you use a different flavor of Linux, you can consult your operating system's documentation for the proper commands and syntax. In many cases, minor variations in syntax and keywords will be the key distinguishment.

As for Asterisk itself, we recommend using Asterisk 18. Unlike other software packages, Asterisk has a large number of versions circulating, a great number of which are currently in use. Systems running Asterisk 1.0 and 1.2, for instance, are plentiful on C*NET. For our purposes, we've not needed any legacy functionality found in these versions that the newer versions lack; however, if you choose to install the Project MF patches on your system, an older version may be required.

Member Compact & Rules

In the interest of transparency and preventing surprises, a few things which are mostly obvious are iterated below.

All NPSTN members, whether they operate a switch or are only hosted members, are responsible for upholding them:

The list is here mainly for transparency. We're a community of friendliness, trust, and cooperation. If that's what you're about, then you'll fit right in!

If a member is unknowingly not compliant with a rule, the NPSTN community is here to help and we'll help you fix any issues you might be having. However, depending on the severity of a violation, members intentionally non-compliant for nefarious reasons may be immediately and permanently banned from the network.

Switch owners generally have a fair amount of leeway and discretion in how they operate their nodes, insofar as it does not interfere with internal network operation. They are free to block calls from the PSTN or other networks on the basis of CVS code, for instance.

Getting Started

New to NPSTN? Looking for some good quick starter documentation that's a little bit more accessible/less comprehensive than this sprawling composition? Check out this awesome guide put together by Famicoman!


First, before doing anything, make sure the timezone on your server is set properly. By default, it will probably be UTC (Universal Coordinated Time, a.k.a. Greenwich Mean Time). If you live in North America, this is of little use to you. You will want the time to be set to your local time so that in the console and in the logs, the times accurately reflect your experience. Even if you don't care, you should still change the time to your local timezone (and if you have a hosted server, you should change it to your timezone, not that of the locality in which the server is hosted). Your server's time is used for various things, including the NPSTN "mock billing system", which depends on accurate timekeeping by each node (more on this in the "Billing" section).

For those on Debian 9, our OS of choice (we recommend Debian 10 now) for running Asterisk, here are some short and sweet instructions on how to change the time.

Essentially, it comes down to the following:

At the shell prompt, type timedatectl list-timezones

Make note of the name that corresponds to your region. Now, change the timezone by typing a command like this one:

sudo timedatectl set-timezone America/Chicago

The above would set your timezone to Central Time.

Now, verify the time is correct by executing timedatectl alone.

After this, there are a few packages you will want or need that are easy to install!

The following packages are not Asterisk-related but are a valuable asset to anyone operating a Linux-based server. Generally, you can install a package from the default Debian repository by typing sudo apt-get install name where name is what you are trying to install. Here are a few must-haves for any server exposed to the Internet:

  • NIST — time synchronization
    sudo apt-get install ntp -y
  • iptables — a versatile firewall, can block IP addresses, ports, etc.
    sudo apt-get install iptables -y
  • tcpdump — can help identify spammer IP addresses
    sudo apt-get install tcpdump -y (to use just run tcpdump port 5060, or whatever your SIP port is)
  • sudo
    apt-get install -y sudo
  • fail2ban — a valuable supplement to iptables, can dynamically block IP addresses after repeated authentication failures. Read more about how to install and configure fail2ban.

fail2ban can be used to block SIP spam in Asterisk, which, combined with changing the SIP bindport away from 5060, can very effectively eliminate spam.

The process essentially comes down to:

  • Installing fail2ban
  • Run touch /var/log/asterisk/security
  • In /etc/asterisk/logger.conf, enable the security log by uncommenting it
  • Add the following to /etc/asterisk/fail2ban/jail.local, creating the file if needed:
    bantime = 1h
    ignoreip = 
    enabled = true
    # if more than 4 attempts are made within 6 hours, ban for 24 hours
    enabled  = true
    filter   = asterisk
    action   = iptables-allports[name=ASTERISK, protocol=all]
    logpath  = /var/log/asterisk/security
    maxretry = 4
    findtime = 21600
    bantime = 86400
  • Run service fail2ban restart. Note that if the security file does not exist yet, fail2ban will crash (hence the touch command)
  • To see blocked IPs that were blocked specifically due to Asterisk-related attacks, run fail2ban-client status asterisk-iptables

Once you have iptables installed, run the following command from the shell command-line:

iptables -A INPUT -p udp -m udp --dport 5060 -m string --string "User-Agent: friendly-scanner" --algo bm --icase --to 65535 -j REJECT

It has been reported that 79% of honeypot traffic gets blocked by this one rule!

Here are some other Linux tools you'll want specifically for Asterisk. They are not required for basic Asterisk functionality but you will want to be sure they are all installed. The rest of this documentation will assume that the following components are available on your system. Here's how you would install them from the Debian command line:

  • wget — a basic tool for downloading files non-interactively (may already be installed)
    sudo apt-get install -y wget
  • curl — a basic tool for extracting the content of online documents (may already be installed)
    sudo apt-get install -y curl
  • sox — for converting audio files to the proper formats for Asterisk
    sudo apt-get install -y sox
  • OpenSSL — for crypto and encrypted calls
    sudo apt-get install -y libcurl4-openssl-dev
  • mpg123 — for playing mp3 streams
    sudo apt-get install -y mpg123
  • dig — for querying DNS servers
    sudo apt-get install -y dnsutils
  • PHP — required for the pulsar (revertive-pulsing) add-on as well as speech-to-text
    sudo apt-get install -y php
  • Festival — required for text-to-speech
    sudo apt-get install -y festival
  • Basic Calculator — required for more complex calculations
    sudo apt-get install -y bc
  • Apache — required for web server
    sudo apt-get install -y apache2 a2enmod ssl a2enmod rewrite a2enmod proxy_http a2enmod proxy_connect systemctl restart apache2

If you need a free SSL certificate, you can use Let's Encrypt to get one:

sudo apt install snapd
sudo snap install core
sudo snap install hello-world
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot certonly --apache
sudo certbot renew --dry-run

Newer versions of Asterisk may have some issues, but here is one list of pre-reqs for Asterisk 18, courtesy S. Y.:

# Install dependencies
apt -y install linux-headers-`uname -r` \
build-essential \
binutils-dev \
git \
bison \
flex \
default-libmysqlclient-dev \
freetds-dev \
libbluetooth-dev \
libcodec2-dev \
libcurl4-openssl-dev \
libedit-dev \
libfftw3-dev \
libghc-postgresql-simple-dev \
libgmime-3.0-dev \
libical-dev \
libjansson-dev \
libneon27-dev \
libosptk-dev \
libnewt-dev \
libradcli-dev \
librust-backtrace-dev \
libsndfile1-dev \
libspandsp-dev \
libspeexdsp-dev \
libsqlite0-dev \
libsqlite3-dev \
libssl-dev \
libunbound-dev \
libvorbis-dev \
libxml2-dev \
unixodbc-dev \
uuid-dev \
xmlstarlet \
libiksemel-dev \
libtolua-dev \
libsrtp2-dev \
libldap2-dev \
libsnmp-dev \

Installing Asterisk

There are several different ways to use Asterisk. Packages like FreePBX exist that offer a GUI (graphical user interface) for configuration, which runs on Asterisk behind the scenes. You can, of course, use "just Asterisk", which is commonly referred to as "vanilla Asterisk", and this is by far the best option and is highly recommended. While GUI-based options like FreePBX may make it easier to get started initially, they are not worth it in the long-run, as you will find yourself restricted in terms of what you can configure, as much of the customizability and flexibility of Asterisk disappears when you use a GUI like FreePBX. We learned this the hard way, as the main NPSTN tandem was running on FreePBX, and Dylan had more than a few problems with it. When the core servers were migrated from HiFormance to VPSCheap, Dylan installed vanilla Asterisk as opposed to FreePBX - and for good reason. The rest of this documentation will assume you are using vanilla Asterisk as it is the only way to truly realize the full power of Asterisk, much of which is tapped into on NPSTN.

While there are many ways to install Asterisk on your system, the best and most reliable way to install Asterisk is by compiling from source. On one occasion, we installed Asterisk from its binaries in the default Debian repository with the result that all of our existing dialplan code didn't work, and other strange issues were encountered as well: differing directory structures, improper reloading of modules, etc.

While providing a tutorial on how to install Asterisk from source is beyond the scope of this documentation, we can point you to some Asterisk documentation as to how to do it.

If you already have an Asterisk server for, say, C*NET, or your own personal VoIP network at home, you can use the same server and the same instance of Asterisk for NPSTN. You have total flexibility with regards to how your Asterisk system is set up. You can choose to reserve the same NPSTN office code as you have on C*NET or you may opt for an entirely different one. You can create different extensions for NPSTN or provide access to the same extensions that your C*NET numbers map to, it's up to you.

WARNING: DO NOT INSTALL ASTERISK WITHOUT INSTALLING ALL THE PRE-REQS! Otherwise, you will probably find later that you are missing something that requires you to recompile and reinstall Asterisk after installing that pre-req. Do it right the first time!

Asterisk 13 was originally recommended for NPSTN, but Asterisk 13 EOL is at the end of 2021 and it is no longer receiving bug fixes. We now recommend Asterisk 18, the newest LTS version. It will be supported the longest out of 16 and 18 and has been more stable than v16 in our experience.

Here are instructions for Debian 10 and the latest version of Asterisk 18 (18.3.0 as of this writing), boiled down to just the commands:

rm -rf /usr/lib/asterisk/modules # optional - if upgrading Asterisk, wipe out all the modules in advance of the upgrade, to remove old modules that aren't in the new versions
cd /usr/src
tar -zxvf asterisk-18-current.tar.gz
cd asterisk-18.3.0
./contrib/scripts/install_prereq install
Change country code from 61 to 1 if/when prompted
./configure --with-jansson-bundled
sed -i 's/ast_answer(chan)/\/\/ast_answer(chan)/g' apps/app_confbridge.c
cp contrib/scripts/ /usr/local/bin
chmod +x /usr/local/bin/

The above sed one-liner simply comments out ast_answer(chan) in the ConfBridge() application code. This prevents ConfBridge() from automatically providing answer supervision, since there is no way to prevent it from doing so through the dialplan. The patch is strongly recommended if you intend to use ConfBridge in any, um, creative ways (since there is often no way to accomplish something in Asterisk without using ConfBridge(), and often times answer supervision is explicitly undesired). This patch will allow you to manually control answer supervision, with the caveat that you must supervise when you intend to. For more details, see Non-Supervising Conference Bridges.

If you need to support older ATAs, run the following two lines — otherwise, TLS will not work with older ATAs (such as the Linksys SPA-2102 and Grandstream HT7xx series), and encryption won't be possible. TLS 1.0 isn't great, but it's better than nothing. If you only have recent/modern ATAs, you can probably skip this step:

sed -i 's/TLSv1.2/TLSv1.0/g' /etc/ssl/openssl.cnf
sed -i 's/DEFAULT@SECLEVEL=2/DEFAULT@SECLEVEL=1/g' /etc/ssl/openssl.cnf

If you do make the changes above, it would not be a bad idea to reboot at this point.

Now, to begin the actual Asterisk compilation process:

make menuselect
Check the following unchecked boxes by using the arrow keys to navigate and pressing ENTER:
  • Add-ons: format_mp3
  • Applications: macro → Macros are now deprecated and should no longer be used, but if you still have some around or would like to be able to run them, this needs to be enabled in v16+
  • Core Sound Packages - Change from GSM to ULAW (required because, unfortunately, the Pat Fleet repository does not replace every single stock recording)
  • Music On Hold: Check ULAW
  • Extras Sound Package: Check ULAW (optional)

Save & Exit.

make samples # do not run this on upgrades or it will wipe out your config!
make install
make config

If you freshly installed Asterisk, open modules.conf for editing (e.g. nano /etc/asterisk/modules.conf) and look for this:

noload =>

If this line is present and you want to continue using the older SIP channel driver as opposed to the newer PJSIP channel driver, replace with Most of the documentation here covers SIP currently, but note that SIP is deprecated and will be eventually removed from Asterisk. However, SIP is also more beginner-friendly and may be a better way to get started.

That's it! Enjoy your new Asterisk install!

Reserving an Office Code

Although you can dial into NPSTN from C*NET or the PSTN using inbounds without configuring your server at all, at some point, you'll probably want to become a full member of NPSTN with your own NPSTN node so you can dial other NPSTN numbers directly. Doing so is incredibly simple!

First, go to the NPSTN Contact Form or call the NPSTN business office and ask to join the network; simply call one of our external access numbers (dial any number that is listed as "terminating to" DISA, from either C*NET or the PSTN, then dial 0). We ask that you be considerate and only claim as many numbers as you actually need and will use. A thousands-block (NNX-X, or 1,000 numbers) should be enough for most people just getting started.

No matter how many numbers you'd like to stake a claim on, check the NPSTN Directory first and make sure someone hasn't already reserved the number blocks you want. For an easier (but more technical) view, you can also check the NPSTN route table which lists all assigned thousand blocks. Note that individual number assignments are not listed in the route table. For example, the number 867-5309 is in use and not available on NPSTN.

If you opt to fill out the form linked above as opposed to calling the NPSTN business office personally, please be sure to provide the following information:

  • Thousand blocks you wish to acquire (NNX-X not NXX-X)
  • Full Name
  • Email Address
  • Telephone Number (either C*NET or PSTN)
  • Server IP Address or FQDN/DDNS
  • IAX Username
  • IAX Password
  • IAX Authentication Mode (None, plaintext, md5, rsa)
  • What you will use your office code for
  • Exchange Name
  • Physical Location, including ZIP code if you are in the U.S.
  • Time zone
  • PSTN NPA (area code) if located in the NANPA and PSTN phone number
  • CLLI — we don't store this for you, but you control your CLLI code via the NPSTN ticketing system.

If you don't know what IAX usernames are, don't worry - they'll be covered in "Initial Configuration". Simply opt for the de facto "npstn" IAX username.

A note about CLLIs is warranted here: you may be familiar with how CLLIs are used on the PSTN — each central office telephone exchange has a unique CLLI. Multiple exchanges (NXXs) may be served off of one physical switch, and consequently, multiple exchanges may share a CLLI. A CLLI is a unique identifier for each switch. You have one CLLI per physical switch, not one per NNX-X or NNX. For our purposes, you can just make up a CLLI for use on NPSTN. Don't be hasty, though! You will need this later and you won't easily be able to change this later on. The CLLI, to be standards-compliant, should be NPSTN followed by 4 to 8 more additional characters briefly characterizing your switch's location and/or function. If you're drawing a blank here or haven't any ideas, NPSTNMS0 (master switch), NPSTNNA01 (North America 1), and NPSTNCFL01 (Central Florida 1) and NPSTNCFL02 are some examples of existing CLLIs on NPSTN. Create a CLLI that makes sense for your switch that matches the format above.

To get with the spirit of 2L+5N dialing (as opposed to 7N dialing), we encourage you to pick out an exchange name for your assigned numbers. Unless you have a good reason not to, it should be a Bell System-approved exchange name. You may wish to consult a list of telephone exchange names. Note that the exchange name you pick does not have to coordinate with adjacent exchanges. As an example, 288 on NPSTN is BUtterfield8 while 285 is, not BUtterfield5, as some might think, but ATwater5.

Exchange names appear in the directory and the Business Office should receive notification of them.

Note:If you only need 10 numbers or fewer, we can host numbers for you. Go to the Hosted Lines tab in the UCP after you've registered.

User Control Panel

Regardless of whether you operate your own node or are hosted on another, you will need an account to access to the NPSTN User Control Panel. This is used to add directory entries and access any mock billing statements issued to you (more on this in the "Billing" section). Contact us with your name, ZIP code, city, and state - as well as timezone and area code - to get this setup. We will email you your auth key (you cannot create an account yourself). Node owners should not add directory entries for others using their UCP key. All members should have their own auth key to access the UCP which they should use to add directory entries for themselves — and nobody else.

Boilerplate Code

The sections "Initial Configuration" and "Common Contexts" contain individual contexts in a well-documented manner for those thirsty for the details. However, for your convenience, if you'd like to get up and running with little attention to the details, you can simply use our boilerplate code. Below, you'll find an essential copy of each of the vital Asterisk configuration files you'll need to get started. You will need to adjust some things, such as properly setting certain variables and defining numbers, and so forth, but the code is otherwise plug and play.

If you are getting started for the first time, backup the files below on your system. Then, replace them with the ones below and go through each file, customizing to the extent required as directed.

  • iax.conf
  • sip.conf — note that SIP is deprecated and will eventually be removed from Asterisk
  • musiconhold.conf
  • verification.conf — a separate dialplan file containing just the code for the verification contexts, to minimize the clutter in extensions.conf — put this in either /etc/asterisk or /etc/asterisk/dialplan
  • extensions.conf

You will need at least a few audio files to get started — the basic call progress tones and intercepts. We encourage all node owners to add variety to NPSTN by giving their nodes their own unique soundscapes. However, if you want to get up and running immediately, we've assembled a set of the basic audio files you will need. Download these files and place them in /var/lib/asterisk/sounds/en/custom/signal/, unless otherwise stated:

Note that the above is not your one-stop shop for getting started! Be sure you have completed everything outlined in the "Pre-Requisites" section above, including changing your server's timezone to your local timezone!

The code above is only a starting point. Many of the contexts individually laid out in this documentation, like the simulated signalling subroutines (e.g. MFer, SFer, dial pulser) are not included in the configuration files above. Only the basics that every node really needs to fully participate in NPSTN are included above. Beyond the contents of the files above, you may wish to further add to your system using other contexts outlined in this documentation.

For maximal network functionality, it is recommended for a node to have the eXtensible dialplan. Many common and helpful routines and contexts are part of the eXtensible dialplan and are updated with new functionality or patches as necessary.

Note: New to Asterisk? No problem — we're here to help! Simply call the business office at 811 on NPSTN and we'll help get you set up.


A word about authentication and encryption:

By default, VoIP, even using IAX2, is not encrypted. Encryption is easy, and it's now included in the boilerplate iax.conf. No other changes are necessary and there are not compatability issues. Some will argue that MD5 is not very robust, but it's better than nothing.

A few notes about IAX2 encryption that are not obvious from looking at the official Asterisk IAX2 or encryption documentation:

  • Plain text authentication does not support encryption (note the difference!). Hence, authentication is a necessary but insufficient condition alone for IAX2 encryption.
  • Encryption requires either MD5 or RSA authentication
  • Plain Text and MD5 authentication use identical configurations and dial syntax. The difference is that MD5 uses a challenge/response rather than sending the password in plain text across the wire
  • A password may be provided even if the dialed destination does not require one. That is perfectly OK. Obviously, if that password is not defined by the peer (and no password is), the call will proceed as usual and it will not be encrypted or authenticated. If the dialed endpoint happens to require authentication and that is, in fact, the correct password, the call will be encrypted.
  • You can tell if IAX2 encryption is working by doing a packet capture in Wireshark. Do a packet capture of an unauthenticated/unencrypted call and then compare with a packet capture of an encrypted call. Import it into Wireshark: the unencrypted pcap will have lots of 7Es, 7Fs, and FFs (if the call was mostly silence); the encrypted call should appear to be mostly random.
  • If you analyze a packet capture of an encrypted call in Wireshark, you may see G.723.1 in the first couple hundred packets. This is nonsense. Wireshark doesn't know the protocol of the packets, since the stream is encrypted.
  • In the Asterisk console, a call is not encrypted if you see Accepting UNAUTHENTICATED call... when a call comes in. It must say Accepting AUTHENTICATED call...

RSA Encryption

RSA is more secure than MD5. However, due to technical limitations and backward-compatability attempts, RSA is only supplementary to non-encrypted or MD5-encrypted "primary" IAX2 users. When supported by both nodes involved in a call, a standardized RSA process will allow for a more securely encrypted call.

RSA encryption has been a stagnant addition to Asterisk for a long time. The key thing to note is that RSA encryption never existed originally in IAX2. RSA authentication predates encryption in IAX2. Encryption was later added, but for plain text and MD5 only (our testing never got to the plain text encryption to work, but that's deprecated now and there's no good reason to ever use plain text). But yes, you read that right. The weak authentication methods got encryption, and the strong one didn't. How much sense does that make? Not?

As early as 2012, people have complained about RSA encryption not working. Digium actually fixed the issue, more or less, in 2014, but the patch was never incorporated into Asterisk. Finally in 2021, after NPSTN decided to revisit RSA encryption in Asterisk, we decided to add it to Asterisk ourselves.

There are a few things to know about RSA encryption that you won't find in the IAX2 documentation, so we're providing them here, straight from the horse's mouth.

First off, res_crypto, the cryptography module Asterisk uses, only supports 1024-bit RSA keys. 2048-bit and 4096-bit keys are not supported. The reason for this is that the only code using res_crypto is the IAX2 channel driver and DUNDI, in Asterisk. Though IAX2 remains popular in some communities today, it never really took in the mainstream VoIP world and is basically dead to its developers, and DUNDI never really took off at all, so it's deader than dead. As you can imagine, there's no incentive for Digium/Sangoma to improve res_crypto by adding support for 2048-bit or 4096-bit keys. If it does get added, it will be by the community (read: this is an open invitation to roll up your sleeves if you'd like to make that happen!)

Secondly, Asterisk needs to know the passphrase to your private key, but it requires this be typed in interactively. Fortunately, 1024-bit keys don't require a passphrase, so it's probably best to not have one at all. The instructions below will show you how to generate a private key that doesn't have a passphrase, which eliminates the need for administrator intervention in loading the keys.

Next, some basic syntax. The Dial() application lets you specify a secret, like this:

Dial(IAX2/iaxuser:[email protected]/5551212)

What you may not have known is that Dial() also lets you specify a private key (or outfile), to use, for the purpose of RSA-authenticated calls:


Here, [privatekeyname] is the name of a private key in /var/lib/asterisk/keys/, without the folder path and without the .key file extension. Just the filename.

Also, notice we said this is how you can make an RSA-authenticated call. Not an RSA-encrypted call. Since the beginning, RSA authentication has not allowed encryption, so this was perfectly sufficient. However, RSA patched to support encryption requires a secret. Well, here is the problem with Dial(). You can't pass both a secret (which was intended for plain text and MD5, only, initially) and a key file name (which was and still is for RSA authentication only). So, you can use RSA authentication without encryption by specifying all the gory details in the Dial() command, but not if you want to use RSA authentication with encryption. Instead, you will need a separate iax.conf peer endpoint for your destination, add all the details there, and then reference that peer in the Dial command, e.g.:



(The above is an example — DO NOT put that in your dialplan).

A quick clarification here. A peer is for an outgoing call. A user is for an incoming call. A friend is for both. (This is for IAX2 only.)


same => n,Dial(IAX2/myrsaendpoint/5551212)

And there you go! At some point, maybe Dial() will be patched to allow providing a secret and a key name. More than likely, it won't be. It's hard to conceive how that could be done elegantly without radically changing the structure of the syntax.

Why are we explaining this? Because it's helpful background to have to understand why the NPSTN RSA code is the way that it is. Essentially, because each outgoing call needs to have a peer corresponding to the destination host, we can't just stuff everything dynamically into the dial string like with MD5 encryption. We need to dynamically write out new peers into a file if necessary. A similar concept applies with adding public keys for incoming calls.

As of the writing of this documentation, the patch to add encryption support for RSA authentication has not been integrated into Asterisk officially yet. You will need to patch your system per the following:

cd /tmp
cd /usr/src/asterisk-18.4.0/
patch -u -b channels/chan_iax2.c -i /tmp/iaxrsa.patch
make channels
make install
asterisk -rx "core restart now"

On NPSTN, RSA encryption is supported for any nodes that use MD5 encryption for their primary NPSTN IAX2 user.

If you are not running Asterisk as root, it goes without saying that keys will need to be readable to the user as which it is running.

Finally, do not use openssl to generate the keys. That is, do not do this:

cd /var/lib/asterisk/keys/
openssl genrsa -des3 -out npstnrsa.key -passout pass:SOMESECRETPASSWORDHERE 2048
openssl rsa -in npstnrsa.key -pubout -passin pass:SOMESECRETPASSWORDHERE -out

For one, that's a 2048-bit key so it won't work anyways. And mercy help you if you add a secret. Instead, use the astgenkey tool provided with Asterisk to generate your keys. It's super easy to run:

cd /var/lib/asterisk/keys/
astgenkey -q -n npstnrsa
cat | curl -X POST --data-urlencode "rsakey=$(</dev/stdin)" -d 'auth_key=YOURNPSTNAUTHKEY' # upload your public key to the NPSTN key repo
touch /etc/asterisk/iax-rsa-npstn-in.conf
touch /etc/asterisk/iax-rsa-npstn-out.conf

Now, open the Asterisk CLI and run the following commands:

module reload res_crypto
keys init
keys show

You should see your public and private NPSTN RSA keys loaded. This is a crucial step.

Now, add this to iax.conf:

type = user
context = from-npstn ; your primary incoming dialplan context, typically from-npstn. THIS MUST BE LISTED FIRST.
context = npstnextensible ; eXtensible dialplan. Not that important for RSA.
context = sntandem ; StepNet. Good candidate for RSA.
context = from-npstn-operator ; Operator. Good candidate for RSA.
context = from-npstn-tieline ; For tie line access. Good candidate for RSA.
auth = rsa
forceencryption = yes
requirecalltoken = yes
secret=yourmd5secret ; this MUST be exactly the same as your NPSTN IAX2 MD5 user secret.
#include iax-rsa-npstn-in.conf ; this file contains the inkeys

#include iax-rsa-npstn-out.conf
Incoming Calls

Initialize iax-rsa-npstn-in.conf with the following contents:

inkeys=npstnrsa ; colon-separated list of public keys to accept

Outgoing Calls

The [dialnpstn-rsa] context is part of the eXtensible dialplan. If your node is not eXtensible, add the context manually to your dialplan by copying and pasting it from the source file.

As you can see, iax-rsa-npstn-out.conf is an automatically managed file containing users for outgoing calls, which will be updated automatically as needed. This subroutine does not contact the NPSTN CRTC at all, since it already has all the information needed.

You need to create iax-rsa-npstn-out.conf, but no need to add anything to it to initialize it. This means you can occasionally clear it out from time to time by running echo "" > /etc/asterisk/iax-rsa-npstn-out.conf from time to time, if you want to get rid of old peers that have piled up every now and then. The dialplan will create new peers as necessary when needed.

All node owners (including those with eXtensible nodes) will need to modify their dialnpstn context, since this is not part of the eXtensible dialplan. For instance:

[dialnpstn] ; NPSTN 20190215 NA, updated 20210518 to add RSA support ; Sends a call to NPSTN
exten => start,1,Set(LOCAL(num)=${ARG1})
	same => n,GotoIf($[${LEN("${ARG4}")}>2]?clidoverride)
	same => n,Set(LOCAL(lookup)=${SHELL(curl "${npstnkey}&lookup=${ARG1}&cid=${CALLERID(num)}&sntandem=${ARG2}&zipcode=${ARG3}")})
	same => n,Goto(verify)
	same => n(clidoverride),Set(lookup=${SHELL(curl "${npstnkey}&lookup=${ARG1}&sntandem=${ARG2}&zipcode=${ARG3}")})
	same => n(verify),Gosub(lookupchan,s,1(${lookup})) ; verifies lookup for extra security
	same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?npstn-BLOCKED,1)
	same => n,Gosub(lookupipcheck,s,1(${lookup})) ; verifies the destination is a public IP address
	same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?:npstn-BLOCKED,1) ; proceed if 0
	same => n(dial),Gosub(dialnpstn-rsa,s,1(${lookup},${num}))
	same => n,ExecIf($["${GOSUB_RETVAL}"="0"]?Dial(${lookup},,gF(autovonpreempted-callee,${autovonchan},1))) ; if RSA dial failed, fallback to primary non-encrypted or MD5
	same => n,Verbose(2, ${CALLERID(num)} -> ${num}: DIALSTATUS: ${DIALSTATUS} / HANGUPCAUSE: ${HANGUPCAUSE})
	same => n,Goto(npstn-${DIALSTATUS},1)

If at first you don't succeed, enable IAX2 debugging with iax2 set debug on.

Most failures occur before the call can get to the dialplan, so this is essential for narrowing down the cause of the issue.

Initial Configuration

At this point, we will assume you have a working Asterisk server and have reached out to get some numbers on NPSTN allocated to you. In the meantime, you can work on getting your exchange setup so that everything will be working by the time your exchange appears in the route table (which usually occurs in less than 1 business day).

A note about Asterisk directories is necessary at this point. For those of you using the regular (compiled from source) version of Asterisk on Debian Linux, directories of particular importance are noted below:

/etc/asterisk/ — where the Asterisk configuration files reside
/var/lib/asterisk/agi-bin/ — where Asterisk AGI programs go
/var/lib/asterisk/moh/ — where music-on-hold audio files reside
/var/lib/asterisk/sounds/en/custom/ — where your custom audio files go (most any audio besides MOH)
/var/log/asterisk/ — where the Asterisk logs are located
/var/spool/asterisk/ — where recordings and voicemails are created

As you spend more time working Asterisk, you'll become more and more familiar with these directories.

To get started, we need to adjust some settings and configure some settings. The following configuration (.conf) files are all located in /etc/asterisk/. You will need to modify them. There are a few ways you can do this. You can opt for the "local modification" approach or the "direct modification" approach.

  • Local Modification — this entails keeping a local copy of all your configuration files, essentially mirroring what is on the server. The principle is simple: perform all your edits locally, and do a one-way write to the server to copy your changes over. If you opt for this approach, you can use a text-editor of your choice, like Notepad++ on Windows, to make changes. If you go this route, you don't need to worry separately about backups, either, since what is on the server in production is just a copy of your local files. If you haven't modified a particular file before, you'll need to copy it over from the server, first.
    If you are using Notepad++, you can add a custom language package to Notepad++ to add dialplan color syntax highlighting.
  • Direct Modification — this entails directly modifying the files on the server. Since there is no GUI, this involves using command-line based text editors, like nano or vim. If you are comfortable with doing complex text manipulation and editing in the terminal, you may find this to be an easier approach. If you go this route, you won't need to worry about version conflicts, but you will need to ensure your configuration files are being backed up (along with your other files, like audio files, etc.!)

Regardless of which method you are using, but particularly if you opt for the local modification approach, you will need an SFTP client, like FileZilla. If you use FileZilla, you will need to change the "Default Transfer Type" to binary. You can do this by going to Edit → Settings → Transfers → FTP: File Types → Default transfer type. Select "Binary" and uncheck the two "Treat files… as ASCII file(s)" at the bottom as well.

You will need to use an FTP client like FileZilla (but using the SFTP protocol) to transfer over your audio files as well as your .conf files each time you make a revision if you opt for the local modification editing approach. You can use your client's site manager to save your connection settings so you don't need to re-enter the connection information each time.

The section below provides detailed commentary and explanation on setting up your Asterisk switch. If you don't care about the details and want to get up and running as soon as possible, see the "Boilerplate Code" section of this documentation. It provides files you can simply copy and paste, rather than providing you with the configuration piecemeal as below.


There are a couple settings you will want to tweak in this file that will impact your debugging. Make sure you have the following options set and uncommented:

verbose = 3
timestamp = yes

By default, the Asterisk console does not give you terribly detailed information as to what's going on when your system is in use. You can manually set the verbosity to, say, 3, for instance, by typing core set verbose 3 at the Asterisk command line interface (or CLI). However, rather than have to manually type this every time you want to debug, it's easier to automatically set the console's verbosity. Verbosities range from 1 through 10 — 1 and 2 are pretty bare in detail, and 10 is not terribly more informative than 3 is (at least that's been our experience), so we'd say setting the default verbosity at 3 is a good starting place. You can tweak this later as you adjust to how much detail each verbosity level provides.

Setting timestamp to yes will provide timestamps in the console, also helpful for debugging. All calls are automatically logged with timestamps, but enabling this will allow you to see your dialplan's execution line by line with important contextual information regarding timing.


Unless you know what you're doing, make sure that you have autoload enabled:


There are a number of VoIP protocols in use today, SIP/PJSIP and IAX2 being two of the more popular ones. There are many others, as well, that should be disabled if you are not using them. Otherwise, they increase the attack surface of your Asterisk system. You can disable other infrequently used or unused modules by adding the following to your [modules] context:

noload => ; Don't load skinny (tcp port 2000)  
noload => ; Don't load MGCP (udp port 2727)
noload => ; Don't load unistim (udp port 5000)
noload => ; Don't load ooh323 (tcp port 1720)
noload => chan_323
noload => ; Don't load dundi (udp port 4520)

The last statement disables DUNDI. If you don't know what that is, you're probably not using it and it's safe to do a "noload" on it. If you are using it by chance, then obviously, don't add that line!

If you so wish, you can look through your modules.conf and/or do some research and disable other modules you are not using. Be absolutely certain not to disable anything you might possibly need! It may help to consult a list of Asterisk modules.


Note that SIP is deprecated and will eventually be removed from Asterisk. PJSIP is the replacement for SIP.

Here, you'll configure the login for your ATA (analog telephone adapter), if you have one. The SIP protocol, due to its popularity, is a common target for abuse, so it is important to have your options set to maximize security. Here is a good starting place:

allowguest=no ;keep intruders out
alwaysauthreject=yes    ;make life difficult for scanners trying to find a way into your dialplan
nat=force_rport,comedia ;should make nat more secure
tos_sip=cs3      ; Sets TOS for SIP packets.
tos_audio=ef     ; Sets TOS for RTP audio packets.
threewaycalling = yes
transfer = yes

While we won't explain all the options here, we will say a thing or two about bindport. Changing this to a value that is not 5060 will change the port that SIP is running on on your server. Changing the SIP port will make it more difficult for attackers and spammers to probe your Asterisk switch unbeknownst to you. Of course, tools like iptables and fail2ban should be relied on to prevent repeated brute authentication attempts (as well as changing the SSH port away from port 22), but changing the SIP port to a random number can further discourage spammers. The idea is to not use port 5060 for external SIP connections by changing your port to something non-standard. For example, you could set bindport=39145. Pick a port between 16383 and 65535 and never tell anyone what port you are using! Do not forward that UDP port in your router and ensure that UDP port 5600 is not forwarded in your router either. Indeed you should not need RTP UDP ports (usually 10000-20000) forwarded either.

The vast majority of VoIP providers use SIP (as opposed to IAX), so this is where you'll register with them if required to do so. At this point, you just need the basic configuration template to get your ATA connected. For example:

[ATAs](!) ; template for all personal ATAs
context = from-internal
type = peer
host = dynamic
qualify = yes
insecure = port,invite
canreinvite = no ; don't allow RTP voice traffic to bypass Asterisk
relaxdtmf = yes
progressinband = yes

defaultuser = ATAx1
secret = verysecretpassword
authid = ATAx1
callerid = "John Smith" <5550101>

The template above can avoid duplicated code if you have lots of ATA ports you want to connect to Asterisk. Obviously, modify the template for your own purposes. If you are hosting other users on your system, you'll probably want to specify the context on a per-ATA basis rather than generally for all ATAs.

SIP can be finicky about getting inband DTMF signals to transmit properly. The best options are SIP Info and RFC2833. This step can require some playing around. To manually set the DTMF mode, add the following option:


This would set the DTMF mode to RFC2833. To change it to SIP Info, use the following instead:


As said before, this can be finicky. Dylan has had the best luck with SIP Info and specifying it explictly in sip.conf. Some have had the best luck with RFC2833 and not explicitly specifying it in sip.conf (and only specifying it in the ATA's configuration options. You'll need to choose an option and try dialing through a DISA or using an echo test to make sure DTMF digits are being received at the distant end.

If you'd like to know more about what these do, consult the Asterisk documentation.

ATA Digit Map

For NPSTN, the ATA's default digit map (also frequently, but erroneously, called a dial plan) will not be sufficient. You will need to tweak the digit map/dial plan to the specifications of your system, but for a basic configuration, the following should be sufficient:




{ 0|[2-9]11|11[2-9]|660|95[89]|10[2-9]xx|100xx|101xxxx|[2-9][2-9]xxxxx|1[2-9][2-9]xxxxx}

The above dial plan can be explained as follows:

  • 0, for the NPSTN operator
  • 11N and N11 codes
  • 660, 958, and 959
  • 10xxx dial-around codes
  • 101xxxx dial-around codes
  • NNX-XXXX dialing for NPSTN numbers
  • 1-NNX-XXXX dialing for 1+ NPSTN (via 2600 Hz trunks)

You may also wish to get dial tone from Asterisk (recommended). That can be accomplished through the use of hotline dialing. For instance, you may wish to automatically connect to extension 20, which will go to an NPSTN dial tone in Asterisk:

However, note that Linksys units will effectively prevent you from flashing with such a digit map, so to actually be able to dial other things when flashing, you may need something more like this:

If you want to a lock a particular phone into hotline dialing a certain extension, the first example is a good way to go. For regular usage, the second method might be more suitable.

The above example is for Linksys-style digit maps. Grandstream uses can configure the off-hook auto dial number (with a timeout of 0) on the appropriate page. If you'd rather hear city dial tone when you pick up your phone as opposed to the precise dial tone generated by your ATA, you can have your ATA auto-dial your NPSTN city dial tone DISA. Most DISAs on NPSTN are NNX-1111, meaning the station number 1111 in each active exchange is usually a DISA. In this documentation, we will pretend we are setting up the 555 (KLondike5) exchange. It would follow that your DISA number would be 555-1111 (or KL5-1111). You could, instead of mirroring your Asterisk dial plan on your ATA and having to coordinate the two, have your ATA auto-dial your DISA. The bonus of this is you don't need to worry about updating your ATA dial plan if you make a change to your Asterisk routing. You also get to hear city dial tone (woo hoo!).

If you do this, you should configure a short code on your system corresponding to a non-billing number, like 20, for instance, go to the dial tone in Asterisk. This way, billing is not invoked for the call.

If you'd like to have your ATA auto-dial a DISA, you can enter the following in the Dial Plan field:


Obviously, change 555 to your office code, and we recommend you change 5551111 to a special, shorter code like 20.

Now, whenever you lift your handset, your ATA will connect to your Asterisk system in a split second and you'll be hit with city dial tone immediately!


First, you'll need to configure your incoming IAX trunks so that NPSTN calls can successfully come into your Asterisk system. If you're not sure whether you have IAX trunking enabled, you can do the following:

  1. First, SSH into your Asterisk server and open your Asterisk console, like so:
    asterisk -r
  2. Run the following command:
    module show like iax
  3. Make sure the IAX2 module is loaded. If it's not, make sure you don't have a noload statement in modules.conf (see the modules.conf section if you're unsure about this).

Once you've determined that the IAX2 module is working, you'll need to modify iax.conf, which is located in /etc/asterisk/.

To start with, make sure that you have some general code in iax.conf that applies to all of your IAX2 trunks. Here's a good starting point:

relaxdtmf=yes ; If your server has issues with DTMF this option may help
delayreject=yes ; for increased security against brute force attacks

Now, in order to give incoming NPSTN calls a route to your dialplan, add the following beneath that:

secret=weyfuyhwgoyhrgwrofghfosdf ; change this to whatever you want, but let the Business Office know what this is or you won't be able to receive calls!

[npstn-operator] ; Incoming/outgoing operator routings (recommended)
secret=npstnoperator362362 ; do not change

[sntandem] ; StepNet (required for StepNet tandems, otherwise unnecessary)

[npstnextensible] ; eXtensible dialplan (optional)
secret=npstnextensible18771871984 ; do not change: this is not supposed to be a "secret". Call the Business Office if you have questions

For direct tie lines between nodes, if desired, create one IAX2 user per incoming or outgoing node:

[npstn-tieline-NPSTNNA01] ; for tieline calls to/from NA01

For general access, it is recommended to add tie line access to the [npstn-rsa] user. The exact access mechanism is not standardized to permit flexibility, since each tieline may be unique and may depend on the nodes involved.

Both nodes could have such a context through which to receive inbound calls OR one node could register to the other and calls be exchanged over that registration. The advantage of the latter approach is that it works even if one of the Asterisk switches is behind NAT, even if it is not directly accessible from the Internet over any port.

Here, [npstn] is the IAX username. If you chose a different IAX username that was not simply npstn, you will need to change the text in the [brackets] to the username you chose.

Here is a breakdown of the different IAX users:

CriticalOptional - RecommendedSpecial-PurposeSpecial-Purpose
npstnPrimary incoming NPSTN user[npstn] is a typical name, but another name may be used for this user
npstn-operatorOperator RoutingsFor operator routings - see inwards.
npstnextensibleeXtensible dialplaneXtensible nodes only
sntandemStepNetStepNet tandems only

Apart from the primary [npstn] user, the names of these users cannot be changed.


At last, extensions.conf, the heart of the Asterisk dialplan! Here is where all (or at least most) of the action happens.

Because you have more flexibility here and there are several options from which to choose here, we've made this a separate section but wanted to mention it here. See "Common Contexts" for some starter dialplan code as well as other contexts, subroutines, and global variables you'll want on your system.

Before continuing, you may wish to reload or restart Asterisk. To restart Asterisk entirely, type core restart now at the Asterisk CLI. To just reload a particular module, you can generally type the module name followed by the reload, such as sip reload. To reload music-on-hold, for example, you would type moh reload. To reload voicemail, you would type voicemail reload. To reload the dialplan (which is something you'll end up doing quite frequently, you'll type dialplan reload. To reload all modules without restarting Asterisk, you can simply type reload.

Occasionally, you may need to reboot your server as well. To do this, from the Asterisk CLI, type exit to quit the Asterisk CLI and exit to the Linux command line. Then type reboot. That's it!


You may find it useful to automatically have voicemail notifications or voicemail messages themselves emailed to you. Asterisk integrates with the system mailer, so if you do not have one configured, you will need to install and configure one. This guide will cover the installation and configuration of Postfix on Debian Linux.

  1. apt-get update
    apt-get upgrade
    apt-get install mailutils
    apt-get install postfix
  2. Choose the default "Internet Site" when prompted.
  3. Enter your domain name for the system mail name.
  4. sed -i 's/inet_interfaces = all/inet_interfaces = loopback-only/g' /etc/postfix/
  5. sed -i 's/mydestination = $myhostname, /mydestination = $myhostname./g' /etc/postfix/
  6. sed -i 's/myhostname = YOURSERVERNAME/myhostname =' /etc/postfix/ ← you may want to manually make this edit using the nano text editor.
  7. systemctl restart postfix

At this point, there are two main options from which to choose. You could use SMTP to send emails using another account (i.e. a Gmail, Hotmail, Yahoo, etc. account), which may be ideal if you already have an account hosted elsewhere you want to use to send messages. Or, you could have messages be sent directly, in which case you will want to ensure that there is an SPF record for the domain in question pointing to your server. Otherwise, sent emails will likely go straight to spam. We recommend using a subdomain specifically for your Asterisk server if you go this route. Each domain or subdomain may have one SPF record. Set up Let's Encrypt on the server if you haven't already done this for hosting a web server, so that email transmission is encrypted.

Trying to work with mail and sendmail in Linux is usually harder than performing brain surgery, so if you get stressed out easily, Linux email configuration and testing is NOT for you. This is because Linux mail programs are not designed for sending mail to the Internet, but sending mail between users on the same system. You will need to do a fair bit of work to fix this or all of your mail will get classified as spam.

To send a test email, run something like this: echo "Test" | mail -s "Test Email" -a "From: [email protected]" [email protected]. Or, you can use this method.

At this point, your messages may be going to spam. Wouldn't it be nice if you knew why? Fortunately, such services exist. Try a few and see what you can improve. Another good one is

One improvement beyond TLS and SPF will be to set up DKIM with Postfix.

Check the error log if things aren't working right: tail -50 /var/log/mail.log.

Finally, in Asterisk's voicemail.conf, make sure that it is configured to send voicemail notifications (and attachments as well, if you prefer) by email and that you have the send from address set properly.

Answer Supervision

All nodes should aim for 100% correct supervisory status at all times for network compliance purposes. This includes supervising on billable/chargeable calls and not supervising when it is not appropriate (e.g. test numbers, intercept recordings, operator services, etc.) We are currently undergoing a network-wide evaluation and compliance program in order to bring all tandem services into compliance.

The most common way to avoid providing false supervision when it is not desired is by using the noanswer flag for Playback() when needed, as follows: same => n,Playback(myaudio,noanswer) — however, note that, to avoid audio passthrough issues, you should use a Progress() statement on calls where there is not immediate answer supervision to allow audio to pass through, e.g:

exten => s,1,Progress() ; allow audio pass through before supervision
	same => n,Playback(myaudio,noanswer) ; play without supervising
	same => n,Answer() ; NOW answer the call
	same => n,Playback(myaudio2)
	same => n,Hangup()

In other words, you should always explicitly begin with a Progress() or an Answer(). We do not recommend relying on implicit supervisory functions of Asterisk applications like Playback().

If your dialplan is already littered with simple Playback commands, you can use the following RegEx in Notepad++'s find and replace feature to locate Playback() statements that don't have the noanswer switch: ^(?!.*noanswer).*Playback.*$

Our friend Read() also provides answer supervision by default. The n option can be used to disable this, e.g. Read(number,custom/file,7,n).

Also note that the revertive pulsing script provides false supervision (which is NEVER desired). You must manually patch this script as described in the Revertive Pulsing section. If you do not patch this script, do not use revertive pulsing on NPSTN. Inpulsing and outpulsing should never supervise.

Finally, advanced dialplan code may involve the ConfBridge() application where it is desired not to supervise. You can patch the Asterisk source code to not automatically supervise conference bridges as described in the Non-Supervising ConfBridge section.

Common Contexts

Here is where you'll find all of the dialplan code you need to get started. Unless specified, it will go in extensions.conf.

Incoming Route

The following will allow NPSTN calls to enter your dialplan:

exten => _sX!,1,Gosub(npstn-verify,${maindisa},1)
	same => n,ExecIf($["${clidverif:-1:1}"!="0"]?Hangup(21))
	same => n,Goto(npstn-public-tielines,${EXTEN:1},1)

exten => _X!,1,Hangup(34)
exten => 1,1,Goto(dt,s,1) ; should go *DIRECTLY* to dial tone (DISA) with NO inpulsing, e.g. dt,s,1
; additional tie lines can be added if you have multiple dial tones e.g. exten => 2,1,Goto(dt2,s,1) or exten => 2,1,Goto(dt,DT2,1), etc.

[from-npstn-operator] ; Incoming calls to/from NPSTN operators
exten => s,1,Goto(000,1)
exten => _X!,1,Gosub(npstn-verify,${EXTEN},1)
	same => n,ExecIf($["${clidverif}"!="10"]?Hangup(21))
	same => n,Set(GROUP(npstnoperatortrunks)=0)
	same => n,ExecIf($[${GROUP_COUNT(0@npstnoperatortrunks)}>10]?Hangup(34))
	same => n,Goto(npstn-operator-route,${EXTEN},1)

exten => _X!,1,GoSub(npstn-verify,${EXTEN},1)
	same => n,Gosub(npstn-blacklist-check,s,1)
	same => n,GotoIf($[${GOSUB_RETVAL}>=2.5]?reject)
	same => n,GotoIf($["${clidverif}"="11"]?:allow)
	same => n,GotoIf($[${GROUP_COUNT(npstnspam@cvs)}<5]?:reject)
	same => n(allow),Log(NOTICE, Incoming call from NPSTN: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,GoTo(external-users,${EXTEN},1)
	same => n(reject),Log(NOTICE, Incoming call from NPSTN rejected: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,Hangup(21)

The "5" only allows a maximum of 5 spam NPSTN calls. This way, if someone with malicious intentions tried to spam your node, only 5 calls would get through; all subsequent calls would get rejected.

See tie lines for more details on the tie line context.

The following will allow calls from your ATA to enter your dialplan:

exten => _X!,1,Log(Local call from ATA: ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,GoSub(clunkcomplete,s,1)
	same => n,GoTo(internal-users,${EXTEN},1)

Sending external and internal calls to separate contexts will give you more flexibility in terms of call routing and private/public destinations. For now, we'll assume that all internal destinations are available to all internal and external callers. To create an internal-only extension, you could create it in a separate context (e.g. [KLondike5-private] that you then include in internal-users).

include => local
include => long-distance
include => invalidincoming

include => local
include => invalidincoming

exten => _${OC1}XXXX,1,GoTo(KLondike5,${EXTEN:-4:4},1)

By creating the [local] context, we can dial locally numbers that have been allocated to us, rather than falling through to [dialnpstn] and doing a lookup and effectively dialing out using an IAX trunk and dialing right back in.

If you happened to own only 555-1XXX, instead of all of 555-XXXX, you would change this line to:

exten => _${OC1}1XXX,1,GoTo(KLondike5,${EXTEN:-4:4},1)

Now, if you dial, say, 555-2638, a match won't be found in [local], so the call will go out to NPSTN. Speaking of which, here is the code necessary to do just that:

exten => 0,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => _11N,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => _N11,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => 660,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => _95[89],1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => _10[02-9]XX,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => _101XXXX,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => _NNXXXXX,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()
exten => _1NNXXXXX,1,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode}))
	same => n,Hangup()

Basic Contexts

Creating a separate context for your exchange helps compartmentalize your code. For example, if you were the owner of the 555 exchange on NPSTN, you might choose to create a KLondike5 context:

exten => 1111,1,GoTo(dt,DTXB,1)
exten => _89XX,1,SayDigits(${EXTEN}) ; your NNX-89XX extensions will read back the last 4 digits of the number called
include => invalidincoming

Here we have included a couple extensions for you to get started. You can easily and quickly create a few more "typical" extensions for your node, in particular a milliwatt number and an echo test (instructions for these are available in the "Vintage Add-Ons" section. Inevitably, you'll create a great number of your own as time goes on, once your system is fully set up. Other extensions you created will go in this context. If you have more than one office code, you can create another context (e.g. [KLondike6]) and add the appropriate line to [local] (e.g. exten => _${OC2}XXXX,1,GoTo(KLondike6,${EXTEN:-4:4},1)).

We have included the _89XX example for newbies particularly (you'll probably want to remove this line when copying it to your node), though this may also serve as a friendly reminder to more seasoned Asterisk programmers. When you do pattern matching (i.e. specifying a range of possible extensions, in this case 8900-8999, you must start with an underscore. Otherwise, it will take it to mean the literal extension "89XX", which is probably not what you want! Any time you match on multiple possible numbers, start with the underscore!

You can change the name of that context to one that makes sense for your exchange, or call it something else altogether. Make sure to update all references to that context.

You can create a line of code for each ATA port you have connected to Asterisk. This code assumes you followed the naming conventions outlined in sip.conf earlier.

[ata] ; Automatically dials the SIP peer by name (ARG1 = SIP peer name)
exten => s,1,Dial(SIP/${ARG1},,m(ringback)g)
	same => n,GotoIf($["${DIALSTATUS}"="CHANUNAVAIL"]?reorder,1)
	same => n,GotoIf($["${DIALSTATUS}"="BUSY"]?busy,1)
	same => n,GotoIf($["${DIALSTATUS}"="CONGESTION"]?reorder,1)
	same => n,GotoIf($["${DIALSTATUS}"="NOANSWER"]?reorder,1)
	same => n,Return()
exten => busy,1,GoSub(busy,s,1)
	same => n,Return()
exten => reorder,1,GoSub(reorder,s,1)
	same => n,Return()
exten => ccad,1,GoSub(ccad,s,1)
	same => n,Return()
exten => acbn,1,GoSub(allcktsbusy,s,1)
	same => n,Return()

Putting your busy and reorder signals in a separate subroutine will allow you to easily change the audio files used for busy and reorder in one place:

exten => s,1,Playback(custom/busy,noanswer)
	same => n,Return()

exten => s,1,Playback(custom/reorder,noanswer)
	same => n,Return()

exten => s,1,Playback(custom/acbn,noanswer)
	same => n,Return()

exten => start,1,Playback(custom/clunk,noanswer)
	same => n,Return()

exten => s,1,Playback(custom/ccad,noanswer)
	same => n,Return()

[clunkcomplete] plays quick "clunk" sound, usually after the caller has finished dialing. You can find an electromechanical clunk sound of your choosing to play here. If you don't have a clunk sound, or would rather not play one, instead of deleting the reference to [clunkcomplete], you can simply modify [clunkcomplete] so it is as follows:

exten => start,1,Return()

If you examine this, you will see that this subroutine does not do anything beside immediately return. However, if you wish to add a clunk when a user finishes dialing a call at some point in the future, you can easily do so by adding a Playback() statement before the Return.

Playback() has a root of /var/lib/asterisk/sounds/en/ so you only need to specify directories downstream from that point. Of course, adjust the Playback() path to that of your audio files. You will need to use SoX to convert WAV recordings of your switch tones to μLaw files; refer to the SoX section in Appendix A for more information.

Notice that the file extension (.ulaw) is not specified. Asterisk will search for the best audio file for the call and automatically select that one. If there is only one file with that name (excluding the file extension), then you can be assured it will be the ulaw file.

Now, let's create the CBCAD context:

exten => s,1,Playback(custom/ccad,noanswer)
	same => n,Hangup()
exten => _X!,1,GoSub(SIPxNumToPeer,s,1(${EXTEN}))
	same => n,GoToIf($["${GOSUB_RETVAL}"=""]?intercept:ata)
	same => n(ata),GoSub(ata,s,1(${GOSUB_RETVAL})) ; Dial SIP peer if it exists
	same => n,Hangup()
	same => n(intercept),GoSub(ccad,s,1) ; Intercept message if SIP peer does not exist
	same => n,Hangup()

[SIPxNumToPeer] ; NPSTNNA 20191130 ; Returns SIP peer of a number or empty string if peer/number does not exist
exten => s,1,Set(num=${ARG1})
	same => n,GotoIf($["${num}"=""]?dne)
	same => n,Set(scriptexists=${STAT(e,/etc/asterisk/scripts/})
	same => n,GotoIf($["${scriptexists}"="1"]?use)
	same => n(download),SYSTEM(wget -P /etc/asterisk/scripts/)
	same => n(perm),SYSTEM(sudo chmod 777 /etc/asterisk/scripts/
	same => n(use),Set(peer=${SHELL(/etc/asterisk/scripts/ "${num}")})
	same => n,GotoIf($["${peer}"=""]?dne)
	same => n,Return(${peer})
	same => n(dne),Return()

We've included a cool helper context here! What SIPxNumToPeer does is translate a phone number to a SIP peer, if one with that ANI exists in your sip.conf. If it does, it will automatically dial the number. This allows dialplan editors to have the convenience of not needing to manually specify each SIP peer in the dialplan while retaining the ability to distinguish between SIP peers that are unreachable and SIP peers that do not exist at all. If you simply in your [invalidincoming] context, as a catch-all, just blindly lazily dialed the SIP peer, assuming it existed, the same CCAD intercept would apply to calls to numbers that don't exist and those that do but are just currently unreachable. This is not ideal, which is why the helper context gives us that power back by telling us whether the number exists or not.

In this sense, the invalidincoming name is a bit of misnomer, because the incoming call may be valid, but we just catch it here if the SIP peer wasn't explicitly specified in the dialplan, and if it exists, we complete it anyways. We don't use a exten => _X! in the main KLondike5 context, because this can create other issues with dialplan matching; a best practice is to always specify catch-alls or fallthroughs in a separate context that is then included.

Obviously, adjust the path specified to that of your CBCAD (cannot be completed as dialed) recording.

Global Variables

At this point, you will want to set some global variables. Add the following to the top of your extensions.conf:

OC1=555 ; "KLondike5"

NOTE: All of these variables will need to be tweaked to match your setup, as follows:

  • clli — this is the CLLI you chose for switch when reserving an office code. For example, if you chose NPSTNSTHTX01 (NPSTN South Texas Switch 1), then change this statement to clli=NPSTNSTHTX01.
  • zipcode — pretty self-explanatory: this is the 5-digit ZIP code in which your switch is physically located. If you have a hosted server, you can choose to set this to either the ZIP in which you personally live or the ZIP in which your server is located (if you happen to know). Note that you should set this to a U.S. ZIP code if you are located in the United States. If you do not live in the U.S., you should set this to 00000, which indicates "international switch" on NPSTN.
  • allowdisathru — this variable should be set to YES by default but can also take on the value NO. When set to YES, incoming calls from another node's DISA will be allowed to make outgoing calls via this node's DISA(s). If set to NO, incoming calls from another node's DISA will not be allowed to make outgoing calls from this node; instead, they will only be allowed to make calls to numbers on this node; other calls will be sent to intercept.
  • allowpstnthru — similar to the allowdisathru global variable; if set to YES, callers from the PSTN that arrived at this node from another node will be allowed to make outgoing calls from this node. If set to NO, callers from the PSTN that arrived at this node from anotehr node will not be allowed to make outgoing calls from this node; instead, they will be "trapped" here — only allowed to make calls to numbers on this node; other calls will be sent to intercept.
  • maindisa — this is the 7-digit NPSTN number of your switch's primary DISA (if you have more than one). Even if you don't have a DISA, set this variable equal to a number that has been assigned to that particular Asterisk switch. For instance, if you owned both 555 and 556, but the 555 exchange went to your home Asterisk server at one IP address and the 556 exchange went to your work Asterisk server at another IP address, if you are configuring this variable on your home server, it must be set equal to some 555 number that has been allocated to you. If you owned just 555-1XXX, it would have to be 5551000 through 5551999. Conversely, if you had both 555 and 556 on the same Asterisk server, and you had DISAs at both 555-1111 and 556-1111, you could set maindisa to either 5551111 or 5561111. But, as the variable's name indicates, it should be the number of one of your NPSTN DISAs on that server, if you have one; otherwise it should be a number on that server (like 5550000), even if it's not necessarily an active extension.
  • usedefaultblacklistresult — this optional variable can be set to 1 to automatically quarantine nuisance calls, allowing you to be a "good neighbor" and watch out for other nodes. The [npstn-blacklist-check] subroutine can be called manually at any time. Data is derived from community-sourced nuisance call reports.
  • npstnkey — this is the 32-character auth id used to log in to the NPSTN User Control Panel. While multiple auth keys may be associated with numbers on one node if that node's owner is hosting others, this variable should be set to the auth key of the node owner. This value is used exclusively for authenticating with the NPSTN database for the mock billing system (more on this in the "Billing" section). The auth key's value is not used in any way; any valid auth key value is merely required in order to prevent spam from entering the billing system. Thus, a node owner using his auth key will not need to worry about it being used in any way in the backend for other users whom he hosts.

  • OC1 — rather than hardcoding the office code 555 throughout your dialplan, it is good practice to use a variable, in this case OC1, in place of the office code. You will need to change 555 to whatever your office code is. If you have more than one office code, you could continue the pattern with OC2, OC3, etc.

You'll probably find yourself adding more global variables at some point in the future, but this is the bare minimum that you need to get started.

Billing Subroutine

There are two versions of the NPSTN billing subroutines. The second version was released to address the primary shortcoming of the first one, which was that it always charged answered calls for the total call duration, rather than only for the duration since the call had answered. The new version is simpler, more elegant, and more realistically tickets calls as they would have been/would be on the PSTN. The original NPSTN billing subroutine is deprecated and should not be used as it will always overcharge on answered calls.


The following subroutine is used as a hangup handler in order to generate a "toll ticket" for each NPSTN call — whether "local" or "long distance".

[npstnticket] ; NPSTNNA 20201213 ; EXTEN = # called, ARG1 = ss, ARG2 = tt, ARG3 = ANI (at time of hangup handler instantiation)
exten => _X!,1,NoOp(DIALSTATUS: ${DIALSTATUS} / HANGUPCAUSE: ${HANGUPCAUSE} / CDR: duration: ${CDR(duration)} / billsec: ${CDR(billsec)} / start: ${CDR(start)} / answer: ${CDR(answer)} / end: ${CDR(end)})
	same => n,ExecIf($["${LEN(${FILTER(0-9,${CALLERID(num)})})}"="0"]?Set(CALLERID(num)=${ARG3})) ; restore original ANI if it was modified later
	same => n,Set(duration=${CDR(billsec)})
	same => n,Set(start=${CDR(answer)})
	same => n,GotoIf($["${CDR(answer)}"=""]?done) ; Don't even try to ticket the call if it wasn't answered (this includes cancelling ticketing on ANI fails, i.e. HANGUPCAUSE=29)
	same => n,ExecIf($["${CDR(billsec)}"="0"]?ExecIf($[${LEN(${CDR(answer)})}>0]?Set(duration=$[${STRFTIME(${EPOCH},,%s)}-${ARG1}]))) ; edge case: calls where called called party hangs up first...
	same => n,ExecIf($["${CDR(billsec)}"="0"]?ExecIf($[${LEN(${CDR(answer)})}>0]?Set(start=${ARG2}))) ; fall back to old way of doing it, use ss/tt, which includes ring time in billing
	same => n,GoSub(npstnbilling2,${EXTEN},1(${duration},${start}))
	same => n(done),Return()

The actual v2 billing subroutine is:

[npstnbilling2] ; NPSTN NA 20201213 NPSTN Toll Ticketing v2; EXTEN= # dialed, ARG1= duration, ARG2= billing start time, ARG3 = type, ARG4= clid override
exten => _[ABCD].,1,GoTo(${EXTEN:1},1) ; Only ARG1 and ARG2 are required ; ARG3 through ARG6 are only used by NPSTNNA01
exten => _X!,1,GoToIf($["${ARG1}"=""]?done) ; Prevent WARNING errors if there are insufficient arguments
	same => n,Set(duration=${ARG1}) ; CDR(billsec)
	same => n,Set(start=${ARG2}) ; CDR(answer)
	same => n,Set(type=${ARG3}) ; optional argument, default=direct
	same => n,ExecIf($["${ARG3}"=""]?Set(type=direct))
	same => n,ExecIf($["${ARG4}"=""]?Set(ani=${FILTER(0-9A-Za-z,${CALLERID(num)})}):Set(ani=${FILTER(0-9A-Za-z,${ARG4})}))
	same => n,Set(callee=${FILTER(0-9,${EXTEN})})
	same => n,Set(request=${SHELL(curl "${ani}&callee=${callee}&type=${type}&duration=${duration}&key=${npstnkey}&clli=${clli}&year=${start:0:4}&month=${start:5:2}&day=${start:8:2}&hr=${start:11:2}&min=${start:14:2}&sec=${start:17:2}")})
	same => n,Return()

To actually trigger billing, simply include the following line in your dialplan, wherever your outgoing routing context is from your dial tone (e.g. [dtroute]).

same => n,Set(CHANNEL(hangup_handler_push)=npstnticket,${EXTEN},1(${STRFTIME(${EPOCH},,%s)},${STRFTIME(${EPOCH},,%Y-%m-%d %H:%M:%S)},${CALLERID(num)})) ; NPSTN Toll Ticketing v2

Note that to work properly, this context must not return to the dial tone. To continue dialplan execution, you should dial it and include the /n option, e.g.:

same => n,Dial(Local/${num}@dtroute/n,,g)

Both of the above subroutines are also automatically included in the eXtensible dialplan. Because the eXtensible dialplan must support all nodes, including NPSTNNA01, which has slightly more sophisticated requirements for [npstnbilling2], the version there is slightly larger but functionally equivalent on all other nodes besides NPSTNNA01. If a node is not eXtensible, it may use the above subroutine which is slightly shorter and simpler to understand.


Below is the original NPSTN billing subroutine. Although it still "works" since there were no API changes between v1 and v2, it is deprecated and using it is discouraged.

[npstnbilling] ; NPSTN NA 20190504 NPSTN Toll Ticketing; EXTEN= number dialed, ARG1= start time of call in seconds, ARG2= datetime that the call started
exten => _X!,1,GoToIf($["${ARG1}"=""]?done)
	same => n,Set(endtime=${STRFTIME(${EPOCH},,%s)})
	same => n,Set(duration=$[${endtime} -${ARG1}])
	same => n,GoToIf($["${ARG3}"=""]?:special)
	same => n,Set(type=direct)
	same => n,GoTo(request)
	same => n(special),Set(type=${ARG3})
	same => n(request),Set(request=${SHELL(curl "${FILTER(0-9A-Za-z,${CALLERID(num)})}&callee=${FILTER(0-9,${EXTEN})}&type=${type}&duration=${duration}&key=${npstnkey}&clli=${clli}&year=${ARG2:0:4}&month=${ARG2:5:2}&day=${ARG2:8:2}&hr=${ARG2:11:2}&min=${ARG2:14:2}&sec=${ARG2:17:2}")})
	same => n(done),Set(__ticketed=1)
	same => n,Return()

You must have the npstnkey and clli global variables properly set in order to use this!

Calls to this are done before a call is made. In circumstances where there is no possibility of more than one call being made by a channel at a time, the following before the call is placed will be sufficient:

Set(CHANNEL(hangup_handler_push)=npstnbilling,${EXTEN},1(${STRFTIME(${EPOCH},,%s)},${STRFTIME(${EPOCH},,%Y-%m-%d %H:%M:%S)}))

The only value above that might depend on your implementation is EXTEN. If the number that was dialed is stored in a variable — rather than being used as the extension of the parent context — this should be adjust appropriately. The subroutine will never need to be modified in any way, nor will any of the arguments in the subroutine call. How you call the subroutine will depend on how call routing flow is defined on your node. When integrating this into a DISA that returns to dial tone upon callee hangup, for example, a hangup handler will not work and more complicated measures must be taken. In our boilerplate template code, we have already integrated this into the dialplan so you don't need to worry about adding this functionality. Integration into DISAs is as complicated as it gets; hangup handlers suffice in very simple DISAs that don't return to dial tone upon callee hangup, or with more basic call flow. However, if you use our template(s), you should not need to worry about this.

If you want to opt out of the mock billing system, simply remove this call. The billing system is only for fun. You will never need to pay the "balance" that appears on your statement. It simply gives users an idea of what they would have paid for their calls in the late 1970s. More information about this is available in the "Billing" section of this documentation.

Music on Hold

At this point, you will want to configure a music-on-hold class for your ringback signal.

Open up musiconhold.conf and add the following:


The [ringback] class is referenced in the Dial Statement in [ata]. If you change the name, make sure to do so in both places.

As written, this context expects your ringback audio file to be located in /var/lib/asterisk/moh/ringback/ and be named rbs1.ulaw. Adjust the paths if needed, but you should keep music on hold files in the moh directory. We recommend creating a separate directory inside of moh for your ringback audio and then placing it inside of that.

If you need a ringback audio file, there are a few available at the NPSTN Sounds Repository. You can also scour Evan Doorbell recordings for something more your style if you're looking for something particular. For example, if you decide your switch is going to be a simulated "Number 5 Crossbar" switch, then you should look for a #5XB ringback you can use (you can use Audacity to prepare the audio and export it as a WAV file to then convert with SoX). Likewise, you should find #5XB busy and reorder signals you can use in your [linebusy] and [linereorder] subroutines.

Now, delete the [default] context and add the following:

random = no


Create the folder silence inside the moh folder and then copy /var/lib/asterisk/sounds/en/silence/10.ulaw into it.

What did this accomplish? In keeping with the realism factor of NPSTN, by default, Asterisk's default music on hold will play if you flash during a call. In most cases, this is undesirable. Not only is it generally frowned-upon in production systems, but music-on-hold on an otherwise simulated electromechanical switch will stand out like a sore thumb. Now, when you flash, the party you just put on hold will not hear anything.

Remember, to reload music on hold you will need to enter moh reload from the Asterisk CLI.

City Dialtone DISA

One realm where NPSTN has been particularly innovative was with its approach to DISAs. Asterisk contains a built in DISA function, but this poses severe limitations for NPSTN. The DISA() function in Asterisk uses the dialtone specifications outlined in indications.conf. This doesn't leave any room for custom audio files to be used as the dialtone; only combined and modulated tones can be used. Though a set of old city tones does exist in indications.conf by default, the results are less than pleasing. A pure 600 Hz modulated by 120 Hz is a far cry from an actual city dial tone which can't be recreated by computer. For maximum realism, a custom audio file must be used for the dial tone.

Hence, most DISAs on NPSTN do not use the DISA() function in Asterisk at all. Instead, custom contexts are used that simulate an old city dialtone very accurately. On some switches, there are unique sounds between each digit as dialing is in progress. By specifying how you want your dialtone to work, line by line, you have full control over its operation. Furthermore, calls complete immediately when the last digit is dialed — users of the DISA() function must generally press # or suffer a timeout, not at all what happened in the old network.

The use of DISA() for any reason is generally discouraged on NPSTN, as it prevents the ability to implement advanced or enhanced features in your dial tone and call routing setup.


Originally, all the dialtones on NPSTN were macros, but this is no longer the case. There are several ways to go about implementing a custom dialtone, and a few of them are covered here. You are advised to opt for a regular context rather than a macro, but here is an example of one of the earlier macros, slightly tweaked for context:

DO NOT use these macros (or any macros in general), unless you are on a very old version of Asterisk that doesn't support subroutines, like Asterisk 1.2. You should use the subroutine version below.

As an aside, most of the code provided in the Docs assumes a relatively modern version of Asterisk, like Asterisk 13 or newer. We do not provide or endorse the use of macros on NPSTN, and they are not supported by default starting with Asterisk 16.

[dialtone] ; Example on how to dial onto NPSTN
exten => s,1,Answer
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=1)
exten => s,n,Read(number,custom/dialtone,7)
exten => s,n,Macro(dialnpstn,${number})
exten => i,1,Hangup
exten => h,1,Hangup
exten => bad,1,Hangup

The above is an extremely basic dialtone that will play the audio file dialtone (located in the custom directory) and dial all NPSTN numbers by performing a lookup, even those that are local. Below is a more complex macro that, still only is designed for 7-digit dialing, but does handle local calls specially:

exten => s,1,Answer
exten => s,n,Read(num,dialtone,7)
exten => s,n,NoOp(Office Code Dialed: ${num:0:1}${num:1:1}${num:2:1})
exten => s,n,GotoIf($["${num:0:1}${num:1:1}${num:2:1}"="${OC1}"]?100:200)
exten => s,100,Answer
exten => s,101,Macro(dl,${num})
exten => s,102,GoTo(1)
exten => s,200,Macro(dialnpstn,${num})
exten => s,201,GoTo(1)
exten => i,1,Hangup
exten => h,1,Hangup
exten => bad,1,Hangup

exten => s,1,Answer
exten => s,n,Set(num=${ARG1})
exten => s,n,Dial(Local/${OC1}${num:3:1}${num:4:1}${num:5:1}${num:6:1}@external-users,${DTL},m(ringback))
exten => s,n,GotoIf($["${DIALSTATUS}"="CHANUNAVAIL"]?400)
exten => s,n,GotoIf($["${DIALSTATUS}"="BUSY"]?600)
exten => s,n,GotoIf($["${DIALSTATUS}"="CONGESTION"]?800)
exten => s,n,GotoIf($["${DIALSTATUS}"="NOANSWER"]?454)
exten => s,400,Macro(dt)
exten => s,600,Playback(custom/busy,noanswer)
exten => s,601,Macro(dt)
exten => s,800,Playback(custom/reorder,noanswer)
exten => s,801,Macro(dt)
exten => s,454,Playback(custom/busy,noanswer)
exten => s,455,Macro(dt)

It looks intricate, but in effect, this is really quite simple. It uses the Read statement to prompt for 7 digits. But what about special numbers like 0 and 911? In order to complete the call immediately when a special number is dialed, more code is required.

In the macro below, in addition to matching on 0, 411, 611, and 911, as well as any 7 digit number, randomly-chosen crosstalk/line noise files are played between each digit. Furthermore, if the dialtone times out (i.e. the entire audio file plays), the line will go to permanent signal.

In both the macro above and the one below, the caller is dropped back to dialtone after the callee hangs up. If this behavior is undesired, you can change the GoTo(1) statements to Hangup(). Otherwise, unless the caller times out to permanent signal (which can also be acheived by pressing # before dialing has begun), the caller will be dropped back to dialtone after each call he makes forever and ever.

exten => s,1,Answer
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=0)
exten => s,n,Set(n1=n${RAND(1,3)})
exten => s,n,Set(n2=n${RAND(1,3)})
exten => s,n,Set(n3=n${RAND(1,3)})
exten => s,n,Set(n4=n${RAND(1,3)})
exten => s,n,Set(n5=n${RAND(1,3)})
exten => s,n,Set(n6=n${RAND(1,3)})
exten => s,n,Read(num1,dialtone,1)
exten => s,n,GotoIf($["${num1}"=""]?ps)
exten => s,n,GotoIf($["${num1}"="0"]?operator)
exten => s,n,Read(num2,${n1},1)
exten => s,n,GotoIf($["${num2}"=""]?dial_complete)
exten => s,n,Read(num3,${n2},1)
exten => s,n,GotoIf($["${num3}"=""]?dial_complete)
exten => s,n,GotoIf($["${num1}${num2}${num3}${num4}${num5}${num6}${num7}"="911"]?dial_complete)
exten => s,n,GotoIf($["${num1}${num2}${num3}${num4}${num5}${num6}${num7}"="411"]?dial_complete)
exten => s,n,GotoIf($["${num1}${num2}${num3}${num4}${num5}${num6}${num7}"="611"]?dial_complete)
exten => s,n,Read(num4,${n3},1)
exten => s,n,GotoIf($["${num4}"=""]?dial_complete)
exten => s,n,Read(num5,${n4},1)
exten => s,n,GotoIf($["${num5}"=""]?dial_complete)
exten => s,n,Read(num6,${n5},1)
exten => s,n,GotoIf($["${num6}"=""]?dial_complete)
exten => s,n,Read(num7,${n6},1)
exten => s,n,GotoIf($["${LEN(${num7})}"="7"]?dial_complete)
exten => s,n(dial_complete),GotoIf($["${num:0:1}${num:1:1}${num:2:1}"="${OC1}"]?dl)
exten => s,n,Macro(dialnpstn,${num1}${num2}${num3}${num4}${num5}${num6}${num7})
exten => s,n,GoTo(1)
exten => s,n(dl),Macro(dl,${num4}${num5}${num6}${num7})
exten => s,n,GoTo(1)
exten => s,n(ps),Playback(custom/permsig)
exten => s,n,Playback(custom/crybaby)
exten => s,n,Hangup
exten => s,n(operator),Macro(dialnpstn,0)
exten => i,1,Hangup
exten => h,1,Hangup
exten => bad,1,Hangup

The problem with this approach is it doesn't scale well. The amount of code required to do complex pattern matching grows exponentially, and furthermore, the use of outdated dialplan syntax (exten => s,n, on each new line as opposed to an indent followed by same => n, can make this a headache to read and debug. With the requirement of immediately completing calls for 0, N11, 10XXX, 101XXXX, NNXXXXX, N0, 1N, 11N, 958, and 959, [macro-dialtone] neared 200 lines of code on its own. Problems plagued [macro-dialtone] almost continually; nesting issues with macros required that [macro-dl] be integrated directly into [macro-dialtone] to prevent stack errors. Eventually, it was decided that the dialtone macro should be rewritten as a regular context.

GoTo Contexts

Ultimately, it was not a single context that was the result of this process but a collection of contexts that, taken together, form the dialtone. Nearly 200 lines of code was split into several contexts that, together, were about 120 lines of code, a slight but sizable improvement in efficiency and readability. The new contexts allow for easier editing and management of the dialtone - as well as greater modularization of components. The same context can be used and different audio files can be played in-between each digit. Different dialtones can be specified. The syntax for using the new [dt] context is more particular, however, and warrants careful inspection.

While this is an extremely sophisticated and intricate dial tone, it is incredibly complicated. If you want a simpler, no-frills dial tone, see the "Boilerplate Code" section of this documentation. The [dt] context contained there is a simpler dial tone that will still complete on the proper number of digits but is otherwise quite basic.

This section presents a heavily simplified version of the dial tones in use on NPSTNNA01. Many of the more sophisticated features and routings require additional modifications or additions.

Below is the general basic structure necessary for this new and improved dialtone:

[numclear] ; NPSTN 20190212 NA
exten => s,1,Return(,,,,,,,,,0,0)

In a fairly straightforward fashion, [numclear] resets the variables used to collect the number being dialed (in addition to a counter used to play the correct interdigit clang sound). This is called at the beginning of this context. This is necessary because calls can return to this context if the Dial statement is used (this is configured in the [dtcheck] context), meaning if the variables are not cleared, digits from the previous call could result in a wrong number if a non-7-digit number is dialed or the # key is used during dialing.

[clanglookup] ; NPSTN 20190212 NA
exten => DTXB,1,Return(dialclang1a,dialclang2a,dialclang3a,dialclang4a,dialclang5a,dialclang6a,dialclang${RAND(1,6)},dialclang7)
exten => _DT.,1,GoTo(DTXB,1)

exten => _DT.,1,Return(0)

[dt] ; NPSTN 20190212 NA
exten => _DT.,1,Answer
	same => n,GoSub(clanglookup,${EXTEN},1)
	same => n,Set(ARRAY(clang1,clang2,clang3,clang4,clang5,clang6,clang7,clang8)=${GOSUB_RETVAL})
	same => n(begin),GoSub(numclear,s,1)
	same => n,Set(ARRAY(num,num1,num2,num3,num4,num5,num6,num7,num8,digit,clangcount)=${GOSUB_RETVAL})
	same => n(digit1),Set(digit=$[${digit}+1])
	same => n(read1),Set(TIMEOUT(response)=0)
	same => n,Read(num${digit},custom/switch/tones/dialtones/${$[${EXTEN}]},1)
	same => n,GoSub(dtlooplookup,${EXTEN},1)
	same => n,GotoIf($["${GOSUB_RETVAL}"="1"]?loopyes:loopno)
	same => n(loopyes),GotoIf($["${$[num${digit}]}"=""]?read1:postloopcheck)
	same => n(loopno),GotoIf($["${$[num${digit}]}"=""]?dtroute,permsig,1)
	same => n(postloopcheck),GoTo(processdigit)
	same => n(nextdigit),Set(digit=$[${digit}+1])
	same => n,Set(clangcount=$[${clangcount}+1])
	same => n,Read(num${digit},custom/dialsounds/${$[clang${clangcount}]},1)
	same => n,GotoIf($["${$[num${digit}]}"=""]?dtroute,${num},1)
	same => n,GotoIf($["${LEN(${num})}"="7"]?dialclang8)
	same => n,GoTo(processdigit)
	same => n(dialclang8),Playback(custom/dialsounds/${clang8})
	same => n(processdigit),Set(num=${num}${$[num${digit}]})
	same => n,GoSub(dtcheck,${num},1)
	same => n,Set(ARRAY(lineaction,linereturn,lineclunk)=${GOSUB_RETVAL})
	same => n,GotoIf($[${lineaction}=TERMINATE]?terminate)
	same => n,GoTo(nextdigit)
	same => n(terminate),Set(TIMEOUT(response)=15)
	same => n,GotoIf($[${lineclunk}=no]?linereturn)
	same => n,GoSub(clunkcomplete,start,1)
	same => n(linereturn),GotoIf($[${linereturn}=yes]?dialyes)
	same => n(dialno),GoTo(dtroute,${num},1)
	same => n(dialyes),Dial(Local/${num}@dtroute,${DTL1XB},g)
	same => n,ExecIf($["${HANGUPCAUSE}"="29"]?Dial(Local/${num}@npstnanifail/n,,g)) ; AST_CAUSE_FACILITY_REJECTED = 29 ; "ANI fail", cancel billing, route call through TSP instead
	same => n,GoTo(begin)

[npstnanifail] ; NPSTN 20201211 NA
exten => check,1,NoOp()
	same => n,GotoIf($["${clidverif}"="11"]?fail)
	same => n,GotoIf($["${clidverif}"="19"]?fail)
	same => n,GotoIf($["${clidverif}"="32"]?fail)
	same => n,Return(0)
	same => n(fail),Return(1)
exten => _X!,1,GoSub(npstn-out-verify,${EXTEN},1)
	same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?npstn-out-blocked,s,1)
	same => n,GoSub(dialnpstn,start,1(0${EXTEN},disable,${zipcode},${maindisa})) ; 0+ @ TSP will handle all billing, if any
	same => n,Hangup()

This is the main context, which calls all of the other contexts presented here at some point or another.

Although this is much denser code that is much more complex than that used in [macro-dt], and is subsequently much harder to follow, it is far more efficient and far more powerful.

The [dtlooplookup] subroutine is called after the first digit and returns 0 if the dialtone audio file is not meant to be looped (default) and 1 if it is. If you have a dialtone audio file that can loop seamlessly (and you would like it to), define an entry for it in this subroutine (as in [clanglookup] that returns 1 rather than 0. Otherwise, 0 will be return and if nothing is entered, the call will go to permanent signal intercept. If 1 is returned rather than 0, the dialtone file will simply play again and it will loop in this manner forever until at least 1 digit is entered. This only applies to the first digit; subsequent audio files will not loop forever if nothing is entered after the first digit.

Of course, there is no reason to modify [dt] at all for an NPSTN node, so you can safely ignore the above commentary. However, we do want to draw special attention to the following line:

	same => n,Read(num${digit},custom/dialsounds/${$[clang${clangcount}]},1)

The above line is designed to play an audio file while the caller is dialing. You can set the audio files that are played (crosstalk, line noise, etc.) but this line expects the audio files returned from [clanglookup] to exist and it will not work without it! If you do not want to play any audio during dialing, replace the above line with the following:

	same => n,Read(num${digit},,1)

Now, even if the audio files referenced in [clanglookup] don't exist, it won't cause any errors and will work normally. Note that provided audio files must be sufficiently long. If they are short, the caller may not have enough time to dial his number.

Now, here are the other contexts required for [dt] to function:

[dtcheck] ; NPSTN 20190212 NA ; value 2 = whether to return to [dt], value 3 = whether to play the clunk sound
exten => 0,1,Return(TERMINATE,yes,yes)
exten => _11N,1,Return(TERMINATE,yes,yes)
exten => _N11,1,Return(TERMINATE,yes,yes)
exten => 660,1,Return(TERMINATE,no,no)
exten => _95[89],1,Return(TERMINATE,yes,no)
exten => _10[02-9],1,Return(TERMINATE,yes,yes)
exten => _101XXXX,1,Return(TERMINATE,yes,yes)
exten => _NXXXXXX,1,Return(TERMINATE,yes,yes)
exten => _1XXXXXXX,1,Return(TERMINATE,yes,yes)
include => dtcheckdef

[dtcheckdef] ; NPSTN 20190212 NA
exten => _X!,1,Return(n,n,n)

The above is equivalent to a digit map (or dial plan) in an ATA. It says nothing about what will happen when a call satisfying those requirements is dialed. It merely reports that the caller has finished dialing or that more digits are still expected. Functionally, you can think of this as the equivalent of an ATA digit map, which only specifies dialable patterns and completes when there is a match. Likewise, [dtcheck] reports when a call can complete.

In addition, when a call is allowed to complete, important contextual information is returned about how to complete the call. As indicated by the comment, the second line (in this case, yes in all three cases), indicates whether to return to [dt] once the call has completed. The actual effect of this is, in [dt], if this option is set to yes, a call is placed using the Dial command (which specifies to return to the beginning once the call is over). Otherwise, GoTo is used instead, which means that the caller will not return to [dt] when the call is over. Any calls completed to regular 7-digit numbers on NPSTN should return to dial tone after the callee has hung up (in other words, yes should be returned for this value).

The third item in the Return statement indicates whether to play the clunk sound specified in the context [clunkcomplete]. If you opted to remove the Playback() statement in your [clunkcomplete] earlier, then it makes no difference whether this is set to yes or no. If you do have a clunk sound, you can specify yes to play it before completing the call or no to skip playing the sound.

In most cases, you will want this option set to yes, meaning a clunk will play whenever a call is placed through the DISA. One scenario in which it might make more sense not to play a "dialing complete" clunk sound is if you are connecting to test equipment located inside the central office, such as a special test circuit a tie-line. Usually, these can be dialed with a 2 or 3-digit code. To preserve realism when dialing a tieline, for instance, it would not make sense to play a clunk before getting another dial tone. If you have a tieline, you can add a separate, more specific rule in addition to the three present that will refrain from playing the clunk. A related case is when a special test number is dialed, such as 660.

[dtroute] ; NPSTN 20190212 NA
exten => _X!,1,Log(NOTICE, Call from ${CALLERID(all)} to ${EXTEN} via ${clli} DISA)
	same => n,Set(CHANNEL(hangup_handler_push)=npstnticket,${EXTEN},1(${STRFTIME(${EPOCH},,%s)},${STRFTIME(${EPOCH},,%Y-%m-%d %H:%M:%S)},${CALLERID(num)})) ; NPSTN Toll Ticketing v2
	same => n,Dial(Local/${EXTEN}@dtdest/n) ; a double dial is unfortunately required to prevent multiple CDR events from messing with the entire CDR for the call (e.g. a Dial followed by Confbridge)
exten => permsig,1,Log(NOTICE, Call from ${CALLERID(all)} at "${CHANNEL(peerip)}" timed out to permanent signal)
	same => n,GoTo(dtdest,${EXTEN},1)

The only thing [dtroute] does is centrally log all calls dialed through the DISA before sending them on to [dtdest]. This avoids having to duplicate code by including a centralized Log statement; otherwise, a Log statement would be required for each extension pattern in [dtdest].

[dtdest] ; NPSTN 20190212 NA
exten => permsig,1,Playback(custom/permsig)
	same => n,Playback(custom/crybaby)
	same => n,Hangup
exten => _${OC1}XXXX,1,GoTo(dtlocal,${EXTEN},1)
exten => 0,1,GoTo(dtnpstn,${EXTEN},1)
exten => _11N,1,GoTo(dtnpstn,${EXTEN},1)
exten => _N11,1,GoTo(dtnpstn,${EXTEN},1)
exten => 660,1,GoTo(dtnpstn,${EXTEN},1)
exten => _95[89],1,GoTo(dtnpstn,${EXTEN},1)
exten => _10[02-9]XX,1,GoTo(dtnpstn,${EXTEN},1)
exten => _101XXXX,1,GoTo(dtnpstn,${EXTEN},1)
exten => _NXXXXXX,1,GoTo(dtnpstn,${EXTEN},1)
exten => _1XXXXXXX,1,GoTo(dtnpstn,${EXTEN},1)
include => invalidincoming

Remember how we said that [dtcheck] was solely responsible for determining if the caller had dialed enough digits to complete the call? Well, [dtdest] is the context that is actually responsible for routing calls made through your DISA. There will always be at least as many pattern matches here as there were in [dtcheck] — and probably more.

First off, you will need the files permsig (permanent signal intercept recording) and crybaby (crybaby "off-hook" tone) for your system. If you don't have these already, you can find samples of these in Evan Doorbell recordings.

Secondly, you will need a "diverter" statement for each office code that you have. If you only have one, you're all set, provided that the OC1 global variable has already been properly set. If you had a second office code, you would need to add the following:

exten => _${OC2}XXXX,1,GoTo(dtlocal,${EXTEN},1)

Next, we'd like to point out it doesn't matter much if the last entry is XXXXXXX or NNXXXXX. The difference is if, say, 2063333 is dialed, with the former, the call will complete to the main NPSTN tandem which will provide an intercept message. With the latter, the call will never make it to [dtnpstn] and will fall through to invalidincoming and you will hear a CBCAD message.

Astute readers, though, may notice that 101XXXX and XXXXXX are separate statements. If you do change the last statement to NNXXXXX, you will need to keep 101XXXX as a separate statement, since any 7-digit call beginning with 101 can't possible meet the criteria for NNXXXXX. However, in this case 101XXXX clearly would fall under XXXXXXX, so isn't the 101XXXX statement redundant, since it does the same thing as the broader statement?

In this case, yes, but that's only because this code has been condensed slightly to create a more basic, less complex dialtone. Here is the original code from the server from which this code was originally taken:

exten => _101XXXX,1,GoTo(dtnpstn,${EXTEN},1)
exten => _XXXXXXX,1,GoSub(registersender,start,1(${EXTEN}))
	same => n,GoSub(sfinteroffice,start,1(${EXTEN}))
	same => n,GoTo(dtnpstn,${EXTEN},1)

As you can see, in this case, 101XXXX calls are differentiated in this case. 101XXXX calls complete immediately here, but all other 7-digit calls include outpulsing. If you wish, you can add your own outpulsing as well. Many of these inpulsing/outpulsing subroutines are included in the "Vintage Add-Ons" section of this documentation.

Finally, here are the contexts through which all 7-digit NPSTN calls pass:

[dtlocal] ; NPSTN 20190212 NA
exten => _NXXXXXX,1,GoTo(local,${EXTEN},1)

Again, [dtlocal] might seem redundant. Why is it necessary if all it is doing is sending the call to yet another context? Here is the same context in its original form from the aforementioned server:

[dtlocal] ; NPSTN 20190212 NA
exten => _NXXXXXX,1,GoSub(mfinteroffice,start,1(${EXTEN}))
	same => n,GoTo(local,${EXTEN},1)

Here, you can see that the number dialed is MFed whenever a local call is dialed through the DISA. You can configure your desired outpulsing and signaling sounds once you add those subroutines.

Also note that, no matter how many office codes you have, the pattern match here should be kept to NXXXXXX (or XXXXXXX or NNXXXXX, it doesn't matter). This is because if a call has landed here, it will have been due to it already being detected to be a local call, so there is no need to further differentiate calls landing in this context. Differences between your office codes should be handled in [local].

Finally, here is the context where all calls that "go out" to NPSTN end up:

[dtnpstn] ; NPSTN 20190212 NA
exten => _X!,1,Set(originalclid=${CALLERID(all)})
	same => n,GoSub(npstn-out-verify,${EXTEN},1)
	same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?npstn-out-blocked,s,1)
	same => n,GoSub(npstnanifail,check,1) ; ANI FAIL CHECK
	same => n,ExecIf($["${GOSUB_RETVAL}"="1"]?Hangup(29)) ; ANI FAIL CHECK - prevent switch from ticketing by hanging up with cause code 29, call will get sent to TSP
	same => n,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode},${maindisa}))
	same => n,Set(CALLERID(all)=${originalclid})
	same => n,Hangup()

The reason Caller ID is reset after the call is callers can potentially make multiple calls in a single session. The verification contexts will potentially modify outgoing CNAM, so resetting this afterward ensures that the altered CNAM doesn't carry over between calls.

It is important that you preserve this context as-is. [dialnpstn] is the subroutine you'll need to make calls to other NPSTN nodes and is available in the next section. [disa-rewrite-cnam] is a subroutine that rewrites CNAM properly for calls that leave your switch, in order to comply with NPSTN standards. You'll find this subroutine later on as well.

Finally, in order to use [dt], we will need to add a global variable:


You've likely already created the [globals] context, in which case you need only add the line beneath that to your existing [globals] context.

The global variable created here references a relative path to your dialtone audio. You can see this in action in this line from [dt]:

	same => n,Read(num${digit},custom/dialtones/${$[${EXTEN}]},1)

In this case, we are expecting that the audio file for the dialtone is called 5xb_dialtone and is located in the directory /var/lib/asterisk/sounds/en/custom/dialtones/city/.

What may not make sense is the ${$[${EXTEN}]} part. Let's review how [dt] is called in the first place:

exten => 1111,1,GoTo(dt,DTXB,1)

As you can see, [dt] is called with the DTXB extension. In this case, that is shorthand for "Crossbar dial tone". The XB should be changed to something that more accurately reflects the type of switch you are simulating and, consequently, the type of dialtone you are using.

When you change DTXB to another name of your choosing (the only requirement being that the name start with DT and it consist of at least one more character, meaning you can't just settle for DT by itself), you will also need to update the following reference in [clanglookup]:

exten => DTXB,1,Return(dialclang1a,dialclang2a,dialclang3a,dialclang4a,dialclang5a,dialclang6a,dialclang7)

By using the type of dial tone as the extension, you can create multiple dial tones with different dialtones and different clangs simply by calling a different extension. For example, you could have DTXY be a Stromberg Carlson dialtone. By adding an entry in [clanglookup], you can use a separate set of inter-digit audio files. You could, for instance, have two different DISAs using different dialtones and different inter-digit audio files by doing the following:

exten => 1111,1,GoTo(dt,DTXB,1) ; a "crossbar" dial tone
exten => 2222,1,GoTo(dt,DTXY,1) ; an "SC XY" dial tone

All you need to do is create a global variable for each DT… extension you create specifying the audio file to use, as well as create an entry for it in [clanglookup] about what interdigit audio files to play (if you chose earlier to disable interdigit audio by modifying that line in [dt] you can ignore this). If you'd like to use the same interdigit clangs for all your DISAs and save yourself some work, you don't need to add an entry; if the DT… extension isn't found in [clanglookup], it'll fall through to [missingclang] and use those audio files as a default.

If you do want to use interdigit audio files, make sure they're all located in the following directory (or modify the code to point it to a directory of your choosing):



The dialnpstn subroutine (or macro) is necessary to make calls to NPSTN.

Original Macro

As stated before, macros are deprecated and don't nest very well, so unless you have a good reason to use macros, you should use the subroutine form. Macros on NPSTN are no longer supported or maintained, but here is the original dialnpstn macro for historical purposes:

[macro-dialnpstn] ; NPSTN DC
exten => s,1,Set(num=${ARG1})
exten => s,n,Set(lookup=${SHELL(curl "${npstnkey}&lookup=${ARG1}&cid=${CALLERID(num)}")})
exten => s,n,NoOp(NPSTN ROUTE TO: ${lookup})
exten => s,n,Dial(${lookup},60,g)
exten => s,n,MacroExit

The above has been fully rewritten as a subroutine that you should use instead. There are additional components in the [dialnpstn] subroutine required to be standards-compliant.

Note that the NPSTN CRTC lookup API requires authentication as of December 2020. The two supported modes of authentication are by auth key and by IP/host. However, authenticating by IP/host is slower, so we recommended always including your auth key. Auth key authentication is supported for any NPSTN account with an associated host.


Here is the [dialnpstn] subroutine:

[dialnpstn] ; NPSTN 20190215 NA ; Sends a call to NPSTN
exten => start,1,Set(num=${ARG1})
	same => n,GotoIf($[${LEN("${ARG4}")}>2]?clidoverride)
	same => n,Set(lookup=${SHELL(curl "${npstnkey}&lookup=${ARG1}&cid=${CALLERID(num)}&sntandem=${ARG2}&zipcode=${ARG3}")})
	same => n,GoTo(verify)
	same => n(clidoverride),Set(lookup=${SHELL(curl "${npstnkey}&lookup=${ARG1}&sntandem=${ARG2}&zipcode=${ARG3}")})
	same => n(verify),GoSub(lookupchan,s,1(${lookup})) ; verifies lookup for extra security
	same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?npstn-BLOCKED,1)
	same => n,GoSub(lookupipcheck,s,1(${lookup})) ; verifies the destination is a public IP address
	same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?:npstn-BLOCKED,1) ; proceed if 0
	same => n,NoOp(NPSTN ROUTE TO: ${lookup})
	same => n,Dial(${lookup},,g)
	same => n,GoTo(npstn-${DIALSTATUS},1)
exten => npstn-ANSWER,1,Return()
exten => npstn-NOANSWER,1,Return()
exten => npstn-BUSY,1,GoSub(linebusy,s,1())
	same => n,Return()
exten => npstn-CONGESTION,1,GotoIf($["${forcesecurechannel}"="1"]?cktsbusy) ; secure calls must be direct, if there is congestion, do not try an alternate route
	same => n,GotoIf($[${DIALPLAN_EXISTS(snroute,${ARG1},1)}]?sntandem) ; StepNet nodes can trunk via StepNet backbone for Alternate Route Selection
	same => n,GotoIf($["${altrouteattempted}"=""]?:cktsbusy) ; Don't try alternate routing more than once
	same => n,Set(altrouteattempted=1)
	same => n,Set(lookup=${SHELL(curl "${npstnkey}&lookup=${ARG1}&sntandem=yes&zipcode=${ARG3}")})
	same => n,Goto(start,verify)
	same => n(sntandem),Gosub(snroute,${ARG1:-7},1) ; try an alternate route via StepNet backbone
	same => n,Set(snrouting=${GOSUB_RETVAL})
	same => n(altroute),Gosub(sndialstring,${ARG1:-7},1(${snrouting:0:4}111)) ; Alternate routing for circuit busy conditions ; send only last 7 in case 1+ calls are congested and failover here
	same => n,Set(lookup=${GOSUB_RETVAL})
	same => n,Set(IAXVAR(snrouting)=${snrouting})
	same => n,Set(IAXVAR(sncount)=3) ; Tandem through one node only (min is 3, this prevents further tandeming)
	same => n(verify),Dial(${lookup},,g)
	same => n,GotoIf($["${DIALSTATUS}"="CONGESTION"]?:npstn-${DIALSTATUS},1)
	same => n,GotoIf($[${LEN(${snrouting})}>4]?:cktsbusy)
	same => n,Set(snrouting=${snrouting:4})
	same => n,Goto(altroute) ; Obtain another alternate routing
	same => n(cktsbusy),Playback(custom/all-ckts-busy-now,noanswer)
	same => n,Return()
exten => npstn-CHANUNAVAIL,1,GoSub(linereorder,s,1())
	same => n,Return()
exten => npstn-BLOCKED,1,Playback(custom/ccad,noanswer)
	same => n,Return()
exten => _npstn-.,1,Return()

The CONGESTION routine automatically uses Alternate Route Selection to trunk a call via the StepNet backbone in case a node is operational but not reachable directly from another node. (This has actually happened to multiple different nodes at various points, so this is not just theoretically useful, it is practically useful). StepNet returns proper answer supervision and congestion handling is done on the far end as opposed to using a hangup cause code.

Note: To use the code as it is presented above, you will need an "all circuits busy" intercept. You should provide Progress() but you should not supervise. If you don't have an all circuits busy intercept, you can change the CONGESTION extension to simply play a reorder signal (like CHANUNAVAIL), but the all circuits busy intercept can be informative in these circumstances if you do have one.

If you don't have or want to use the necessary audio files, you can use PlayTones(busy) to provide a precise busy signal and PlayTones(congestion) to provide a precise reorder signal. Note that these applications do not block, so you must include a Wait() statement with a numeric argument or the call will continue and you may not hear anything at all.

ARG1 is the number on which to do a lookup.

ARG2 (usually disable) is whether or not to activate StepNet (see the StepNet section for more on this). This way, if you do want to use StepNet trunking, all you have to do is instead of calling GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode})), call GoSub(dialnpstn,start,1(${EXTEN},enable,${zipcode})).

ARG3 is your ZIP code (refer to "Reserving an Office Code" for more info on this), but you should use the zipcode global variable rather than specifying it manually. That way, if you move, you only need to change the global variable's value. The advantage of specifying the ZIP code in each call rather than having the zipcode variable used automatically in the dialnpstn subroutine is that if you need to change the ZIP code in one particular circumstance, you can do so easily without modifying the dialnpstn subroutine at all. If are located outside of the US, your zipcode global variable should be set to 00000.

ARG4 is an optional argument and is usually not used. The one case in which it is used is in [dtnpstn], where ${maindisa} is passed in. This will NOT set the ANI of the caller to the lookup request. This way, if a caller dials into your DISA and dials himself right back, he won't get a NOYOL intercept (Number on Your Own Line). The way the lookup API works is if the Caller ID argument matches the lookup argument, instead of returning that number's actual destination, it plays a NOYOL message and then rings you back. Although we choose to handle all local calls locally, if you sent everything to [dialnpstn], you would get this service whenever you dialed your own number. Since this is not desired behavior with network DISAs, ARG4 prevents ANI from being sent to the lookup (as it is omitted from the lookup), allowing callers to dial themselves normally through DISAs, instead of getting NOYOL intercepts.

In case you're feeling left out if you don't send local calls to [dialnpstn], don't! You can manually use this service from any line by calling BE1-9061. This is the NOYOL ringback. If you just want plain and simple ringback, 959 is the network-wide test number to do that.

The subroutine call to lookupchan is intended as an extra security measure. If the API, hypothetically, were to be compromised, this subroutine call will prevent potentially fradulent lookup returns from allow malicious callers access to sensitive areas of your switch. Note that the lookupchan and loopupipcheck subroutines are part of the NPSTN verification subroutines and can be found in the "Verification" section of this documentation.

One unrelated difference between the original macro and the subroutine here is the curl statement used by each (ignoring the sntandem and zipcode flags in the subroutine):
Set(lookup=${CURL(${npstnkey}&lookup=${ARG1}&cid=${CALLERID(num)}))} as opposed to
Set(lookup=${SHELL(curl "${npstnkey}&lookup=${ARG1}&cid=${CALLERID(num)}")})

This is an unrelated difference; not all Asterisk systems support the former, but the latter should work for everyone. You can try out the former line, but if it doesn't work, try using a SHELL command that invokes curl as prescribed in the latter statement, as opposed to trying to use CURL directly.

Macro for Older Asterisk Systems

Some Asterisk systems are so old that CURL can not be used directly from within Asterisk in any way. Furthermore, not only are subroutines not supported, but even macros don't work in the same way. Here is an implementation to workaround that for older systems, such as Asterisk 1.2. These instructions are provided courtesy of Don Froula, who uses this to access NPSTN from his Asterisk 1.2 Project MF switch:

Curl must be compiled with openssl of at least 1.0.2. I downloaded 1.0.2r and compiled it on my Debian 4 "Etch" installation.

I then uninstalled the default curl package with and compiled and installed curl 7.59.0, which picked up the newer openssl libraries. (Complete Instructions)

I followed the instruction exactly and it worked perfectly. After upgrading, test the NPSTN connection from curl with: curl -v${npstnkey}&lookup=5551212

The output should spit out a verbose listing of handshaking details, with the resolved dial string on the final line (no CR/LF).

Here is the macro:

exten => s,1,System(/bin/rm -f /var/spool/asterisk/monitor/npstn.txt)
exten => s,n,System(curl -o /var/spool/asterisk/monitor/npstn.txt${npstnkey}&lookup=${ARG1}&cid=${CALLERID(num)})
exten => s,n,Wait(1)
exten => s,n,ReadFile(lookup=/var/spool/asterisk/monitor/npstn.txt,255)
exten => s,n,NoOp(NPSTN ROUTE TO: ${lookup})
exten => s,n,Dial(${lookup},60,g)
exten => s,n,MacroExit

The macro gets around the lack of a "shell" command on earlier versions of Asterisk by directing the curl output to /var/spool/asterisk/monitor/npstn.txt, the reading it back in with ReadFile. The delay after the curl command is needed to give curl time to open, write, and close the file.

Verification Subroutines

PRE-REQUISITE: You MUST have DIG installed on your server (part of the dnsutils). Please see the Pre-Requisites section for more details.

The verification subroutines rely on the following global variables being properly defined: maindisa, allowdisathru, and allowpstnthru.

Here is a list of the 2-digit "caller verification status codes" used on NPSTN. The network name on the left refers to the original source of the caller. The first upstream node (the node into which an external caller originally dials) determines the code below to use and passes it along to downstream nodes.

  • 00 — Reserved, for local temporary use (e.g. if sending a call over a trunk that doesn't pass channel variables, temporarily turn null CVS into 00 and add that to the extension, then convert back on the other end)
  • 10 — NPSTN, valid and legitimate
  • 11 — NPSTN, valid but illegitimate
  • 19 — NPSTN, untrusted/illegitimate (validity spoofed by upstream node)
  • 20 — C*NET, valid and legitimate
  • 21 — C*NET, valid but illegitimate
  • 30 — US PSTN, valid
  • 31 — US PSTN, invalid
  • 32 — US PSTN, anonymous
  • 40 — UK PSTN, valid
  • 41 — UK PSTN, invalid
  • 42 — UK PSTN, anonymous
  • 50 — AU PSTN, valid
  • 51 — AU PSTN, invalid
  • 52 — AU PSTN, anonymous
  • 60 — Other PSTN, valid
  • 61 — Other PSTN, invalid
  • 62 — Other PSTN, anonymous
  • 90 — Other, valid
  • 91 — Other, invalid
  • 92 — Other, anonymous

Generally, X0 indicates the call is valid. Note that we can only confirm the caller if the CVS (caller verification status) code is 10 or 20. 30 or 40 indicate PSTN calls that, while they appear to be valid, cannot be 100% confirmed to be so. 9X codes are reserved for other use, such as assigning CVS codes to other private networks. This way, such calls do not have an empty CVS code and do not appear to be originating locally when performing CVS branch checks.

The verification subroutines will be updated in early 2021 to incorporate STIR/SHAKEN headers as part of the verification process for incoming PSTN calls from VoIP providers.

If the last digit the CVS code is not 0, the caller ID is almost definitely spoofed, unless the last digit is 2, which indicates an anonymous PSTN caller whose identity, while not necessarily spoofed, cannot possibly be verified.

Note that validity and legitimacy here are two different concepts. Validity refers to a number being of proper format. PSTN calls can be determined to have a valid number, based on the rules specified for PSTN numbering, but verifying that number is a legitimate is different altogether. Legitimacy can only be easily determined for NPSTN and C*NET callers.

CVS codes are frequently used to provide different classes of service. For instance, if you have sensitive contexts to which you wish to allow only trusted callers, you can, in addition to blocking calls with "via NPSTN" in the CNAM (which will block calls made via other nodes' DISAs or a via a node's PSTN gateway), choose to allow only calls with a CVS code of 10 or 20, as you can at least trust these callers are who they say they are.

You can also use CVS codes to act accordingly in your dial plan based on the origin of the caller, i.e. sending NPSTN callers to one context and C*NET callers to another. All you would need to do in this case is check the first digit of the CVS code to determine from where the caller originally dialed in. The second digit says more about the caller's verification status. Since the first digit is network status and the second is verification status, you could, for example, use GoToIf($["${clidverif:-2:1}"="1"]?npstn) and GoToIf($["${clidverif:-2:1}"="2"]?cnet) within your dialplan.

WARNING: If you use CVS codes to configure "forks" in your dialplan, you must always define an exception for calls where the clidverif variable is not defined, which will be the case for any local calls. Since all incoming calls to your node will be assigned a CVS code, if a call does not have a CVS code, it is a local call and you should handle it accordingly, like so: GoToIf($["${clidverif}"=""]?local). If you don't add this "null variable exception", any calls originating from your switch will not route properly at any such forks in your dialplan.

At this point, you may wonder why using IAX variables to pass around these 2-digit codes is necessary. If you care to examine the code carefully, the reason becomes obvious, but the quick answer is that it is only possible to accurately verify calls from the last upstream node. If a caller dials through a DISA and then dials to another node, this third node cannot possibly verify the original caller. This is because while its Caller ID is the original Caller ID, the IP address from which the caller will appear to be coming belongs to the second node. Hence, if a simple IP verification technique is used, the caller will be flagged as fradulent. Hence, it is necessary for each upstream node to "pass along" the results of its verification tests to subsequent downstream nodes. In this way, downstream nodes can be as sure of the validity of the caller as if he had dialed that node directly. Furthermore, the validity of the upstream node itself is also verified to be sure we can trust its judgment (i.e. to make sure a rogue impostor server pretending to be an NPSTN node is not fradulently attempting to pass along inaccurate verification information). Hence, the ${maindisa} variable is used to allow a downstream node to do a reverse lookup and confirm the node is who it says it is. With these subroutines, in conjunction with the CNAM subroutines below, it is impossible for a non-NPSTN node to fradulently present itself to any node, no matter how far up or downstream in a call a node may happen to be.

Please be aware DIG (one of the NPSTN pre-requisites) is required for these subroutines to work properly.

[dtnpstn] patch

First, you'll need to modify your [dtnpstn] context; previously, it was something like this:

[dtnpstn] ; NPSTN 20190212 NA
exten => _X!,1,Set(originalclid=${CALLERID(all)})
	same => n,GoSub(npstnanifail,check,1) ; ANI FAIL CHECK
	same => n,ExecIf($["${GOSUB_RETVAL}"="1"]?Hangup(29)) ; ANI FAIL CHECK - prevent switch from ticketing by hanging up with cause code 29, call will get sent to TSP
	same => n,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode},${maindisa}))
	same => n,Set(CALLERID(all)=${originalclid})
	same => n,Hangup()

Now, patch it to the following:

[dtnpstn] ; NPSTN 20190215 NA
exten => _X!,1,Set(originalclid=${CALLERID(all)})
	same => n,GoSub(npstn-out-verify,${EXTEN},1)
	same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?npstn-out-blocked,s,1)
	same => n,GoSub(npstnanifail,check,1) ; ANI FAIL CHECK
	same => n,ExecIf($["${GOSUB_RETVAL}"="1"]?Hangup(29)) ; ANI FAIL CHECK - prevent switch from ticketing by hanging up with cause code 29, call will get sent to TSP
	same => n,GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode},${maindisa}))
	same => n,Set(CALLERID(all)=${originalclid})
	same => n,Hangup()

Note that this assumes that all outbound NPSTN access available to any external callers is through your city dial tone DISA. If you use the DISA() function in Asterisk, you can create a DISA context and send all non-local calls to the [dtnpstn] context as a workaround. However, the use of DISA() is not recommended for any reason.


Hopefully all the audio files you need are on your Asterisk switch by now. Once you've modified your files (and copied them over to the server if you modified them locally), be sure to enter dialplan reload at the Asterisk CLI prompt. Take care that you don't see a WARNING message when you hit Enter; if you do, debug the error before continuing.

You won't need to do this every time you update your dialplan, but since we modified several files here, it would be a good idea to just reload Asterisk in its entirety and restart it:

core restart now

If you do happen to be using FreePBX, you can reload by typing fwconsole reload. In vanilla Asterisk, you can also try reload asterisk.

Once Asterisk exits, enter asterisk -r to enter back into the Asterisk CLI.

IP Security Check

The IP check portion of outbound call verification ensures the resolving address is in the public address space. This mitigates against a lateral attack vector that a malicious privileged caller could exploit to move laterally within a private address space. For details about this vulnerability, see this whitepaper.

Fortunately, it's not a severe issue and the fix(es) is/are simple. One method you can use is by adding this simple line of code on outbound calls, in your [dialnpstn] context. You can place it after the lookupchan test which ensures the IAX string is properly formatted and not malicious in other ways. Find this code in your dialplan:

same => n(verify),GoSub(lookupchan,s,1(${lookup})) ; verifies lookup for extra security
same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?npstn-BLOCKED,1)

To patch this particular vulnerability, add the following code immediately afterwards:

same => n,GoSub(lookupipcheck,s,1(${lookup})) ; verifies the destination is a public IP address
same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?:npstn-BLOCKED,1) ; proceed if 0

The dialnpstn subroutine provided in the Docs already contains this change, as does the default boilerplate code's dialnpstn subroutine (as of 10/6/2020 only).

lookupipcheck returns 1 if the address is a private Class A, B, or C address, 0 if it's a public address, and -1 if it's the loopback address.

The referenced subroutine is part of the standard verification subroutines. If your node is eXtensible, you will already have the latest copy. Otherwise, you will need to reload your verification subroutines. You can manually download the latest copy at any point by going to the link above.

Unlike many other aspects of the verification subroutines, this outbound verification process is not automatic. The verification subroutines includes the necessary subroutines to perform this security check, but you must be sure to manually call it in your dialplan in the relevant places. Any place in your dialplan that an arbitrary non-trusted (external) user could result in a lookup being performed, it is recommended that you perform an IP check. The call will fail to go through if it is determined that the IP has resolved to an address in the private IPv4 address space (either Class A, B, or C) as well as link-local loopback and APIPA addresses.


The following information has been compiled courtesy of Don Froula:

For those using Windows machines on their LANs, you may wish to explore a very useful program that I have been using for many years. The Windows "NetCID" program receives an IP broadcast on your LAN from an Asterisk PERL AGI script that goes out to ALL local IP addresses on your LAN on a special port, where a popup and optional sound alerts to the incoming call. The contents of the popup may be customized in the AGI script. I have five different scripts to indicate if the call is coming from the PSTN, CNET, NPSTN or other sources.

You must have PERL installed on your Asterisk machine.

The program is quite old, and a bit hard to find. It does work fine on all versions of Windows, including Windows 10.

You can download the program here. Instructions for setting up the AGI and how to call it in your dialplan are available at VoIP-Info.

In the AGI script, set the first three octets of the IP address to match those of your LAN. Leave the last octet at 255, which signifies the data is to be broadcast to all devices on the LAN. You may need to allow IP broadcasts on your router. Do NOT change the port number.

You may modify the following to customize the popup display with hard-coded text that appears at the bottom of the screen. Change nothing else. Example:

my $MSG1 = “STAT NPSTN Phone Call to $extension”;

You can call the AGI script from the dialplan like this:

exten => _X.,n,AGI(ncid4.agi|${CALLERID(num)}|${CALLERID(name)}|${EXTEN})

Note I have multiple versions of the script to display different text at the bottom of the large-sized popup. This is ncid4.agi (for CNET) to adjust as needed.

My old router, since replaced, did have a setting that blocked IP broadcasts on the LAN, although it seems that the broadcasts should not be controllable through the router as they are peer<>peer. When I install NetCID on Windows 10, Windows defender pops up and asks if I want to allow NetCID traffic on my LAN. I don't think this is default behavior in Windows 7. This action openrd all TCP and UDP ports for the NetCID application, but in reality it only uses UDP port 42685, so opening that up should be sufficient.

I installed Asterisk as root. My /var/lib/asterisk/agi-bin/ directory is owned by root with directory permissions of 750. The ncid.agi scripts themselves are 777.

You can change the IP address to target one machine on the LAN to see if broadcasts are being disallowed on the Asterisk machine or the target PC.

The "sleep 5" commands send periodic RING indications to the NetCID app to keep the popup on the screen if the "display until ringing stops" option is set on the NetCID app.

I think there is a typo on the first line of the AGI script on the web page. Note the first line must begin with "#!/usr/bin/perl", making sure the path to the PERL executable is correct.

Here is my working CNET script:

use Socket;

open STDOUT, '>/dev/null';
fork and exit;

my $timedata = localtime(time);
my $cidnum = $ARGV[0];
my $cidname = $ARGV[1];
my $extension = $ARGV[2];

my $MSG1 = "STAT CNET Phone Call to $extension";
my $MSG2 = "RING";
my $MSG3 = "NAME $cidname";
my $MSG4 = "TTSN Call from $cidname";
my $MSG5 = "NMBR $cidnum to $extension";
my $MSG6 = "TYPE U";
my $MSG7 = "IDLE $timedata";

my $ipaddr=;
my $portnum=42685;

socket(SOCKET, PF_INET, SOCK_DGRAM, getprotobyname("udp")) or die "socket: $!";
setsockopt(SOCKET, SOL_SOCKET, SO_BROADCAST, 1) or die "setsockopt: $!\n";
send(SOCKET, $MSG1, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG2, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG3, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG4, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG5, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG6, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG2, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG2, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";
send(SOCKET, $MSG7, 0, sockaddr_in($portnum,$ipaddr)) or die "cannot send to $HOSTNAME($PORTNO): $!";

There are quite a few options that can be set in the NetCID application, but you need to open them when from the tiny icon in the system tray in the lower right corner of the Windows desktop. Right click on the little TV-looking icon and some options that can be checked or unchecked will pop up. Choose "Configure" for many more. You may need to click the "^" to see the full list in the system tray.

You might try running the script from a Linux terminal window as a PERL script directly, rather than as an SGI script through Asterisk for debugging. That would eliminate a few variables.

perl /var/lib/asterisk/agi-bin/ncid.agi 5555555 "John Smith" 5554444

That invocation pops a window up on all my PCs in the house.

Potential Problems:

The script copied from the web site has open and close double quotes which are apparently not recognized by perl on my system. I found it when I tried to invoke the script from the command line and got error messages about an unrecognized character on a certain line. I changed all the open close double quotes to regular double quotes and all was well. It works from the command line and more importantly, from Asterisk as intended.

Tielines (and FX lines)

The tieline context allows for direct calls between nodes, mirroring the way tielines connected PBXs and exchanges in the 20th century. One way these could be used is as follows:

exten => 812,1,Gosub(npstn-tieline-out,s,1(npstn-tieline:[email protected],s1@from-npstn-tieline))
	same => n,Hangup()

For a peer to peer "ad hoc" call, username:password@host would be the right argument. To reference a registration, only the local IAX2 user name corresponding to the registration or peer would be used.

exten => s,1,GotoIf($["${clidverif}"=""|"${clidverif}"="10"]?:congestion)
	same => n,Gosub(npstn-out-verify,${maindisa},1)
	same => n,Dial(IAX2/${ARG1}/${ARG2},,g)
	same => n,ExecIf($["${HANGUPCAUSE}"="16"]?Return)
	same => n,GotoIf($["${DIALSTATUS}"="INVALIDARGS"|"${HANGUPCAUSE}"="21"|"${HANGUPCAUSE}"="34"]?congestion)
	same => n,Return()
	same => n(congestion),PlayTones(congestion)
	same => n,Wait(10)
	same => n,Return()

To receive incoming tieline calls, send all tieline calls from individual IAX2 users (one for each node from which you might receive a call) here:

[from-npstn-tieline] ; incoming dialplan context for tie line calls from all nodes (each node gets a separate IAX2 user, though, for security et. al. reasons)
exten => _sX!,1,Gosub(npstn-verify,s,1)
	same => n,ExecIf($["${clidverif:-1:1}"!="0"]?Hangup(21)) ; allows any verified caller, independent of network (to restrict to NPSTN, change -1:1 to -2 and "0" to "10")
	same => n,Gosub(log,s,1(Incoming tie line call: '${clidverif}' ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN},0))
	same => n,Goto(npstn-public-tielines,${EXTEN:1},1)

exten => _X!,1,Hangup(34)
exten => 1,1,Goto(dt,s,1)
; additional tie lines can be modified

The above convention is merely a suggestion; two nodes could use any arbitrary numbering scheme to refer to tielines, as long as they are both on the same page about it. However, if a number of incoming tie lines need to be managed, a convention like the above may simplify the process.

Note that the tieline verification process is slightly different than the regular verification process. The ${maindisa} number on the outgoing switch is ALWAYS used for verification, instead of only on DISA-hopping calls. With a newer version of the verification subroutines (at least 03/2021), DISAVIA numbers are cached. Thus, after one successful call (either regularly on NPSTN or over the tieline), the node's number will be cached, so even in the event of a CRTC or NPSTN lookup failure, tieline calls would not fail, and security is not sacrificed either (best of both worlds)!

The caching part of the verification subroutines is only used for DISAVIA numbers, so a node will only cache one number per NPSTN switch, the minimum necessary for verification to work during a lookup failure. In the event of a lookup failure, calls could be manually routed around the network using tie lines (by those aware of the proper routing codes).

For public tie line codes, if you will have at most two outgoing tie lines, the codes 81 and 80 are recommended; if you require between two and eight tie lines, the codes 812 through 819 (i.e 81N) are generally recommended, since 80X and 81N are invalid NPSTN exchanges (811 is the Business Office and 8NX are valid exchanges). For obvious reasons, 8 alone cannot be used. The above codes apply to the NPSTN numberspace (e.g. an NPSTN dial tone), ideal if you want DISA callers to be able to use the tielines as well. For private tielines outside of the NPSTN numberspace, 1XX tielines (CENTREX only) or 8/8X/8XX are common codes when the 8 prefix does not conflict with a CCSA. (Other codes were also used, if less often, such as 44X and 45X in the Evan Doorbell Recording "Exploring Life Insurance Tielines".)

Tie lines should not introduce answer supervision on their own, so [dt] should have Progress() but not Answer() - and the n option should be used with Read (and noanswer with Playback) to prevent supervision

On NPSTN tie lines may or may not directly go to an NPSTN dial tone. Tie lines are, by definition, decentralized, and each tie line may be configured differently. Arrangements may vary. Note, however, that a few wrappers are built around tie lines to allow for CVS (verification) status to pass between nodes, even on tie line calls, ensuring robust security and verification even on tie line calls that do not use any NPSTN lookups. Such a wrapper should be used if an outgoing/incoming tieline call has any chance of terminating to an NPSTN number after going over the trunk.

Tie lines are private trunks between switches through which a number of calls may be exchanged in one or both directions. An FX (foreign exchange) line, in contrast, has a specific station number on the foreign switch and is a simply a line served out of a different switch, terminated directly to a telephone (e.g. an ATA) or a PBX or CENTREX. So, if you had some hosted numbers off another member's switch, but your primary lines were served out of a different switch, the foreign lines are FX lines. A key difference is that FX lines work like subscriber lines, not trunks, and have a specific number associated with them. Tie lines are trunks that should not provide answer supervision (but should provide progress before the dial tone) and may not necessarily have a specific number, allowing one switch to pass ANI through the tie line.

Inward Routings

Inward operators on NPSTN function much as they do/did on the PSTN. While the NPSTN network operator will be able to help with most dialing difficulties, a network operator may occasionally have need to request the assistance of a particular exchange's inward operator, in order to diagnose line difficulties or complete calls.

All NPSTN node owners may appoint and allocate any destination as the inward operator for some particular numberspace that they own.

The exchange owner with the largest ownership share of an office code (NNX code) will be granted rights to the incumbent/primary routing of that code. Additional inward operators will be reachable by also including a routing code to bypass the main NNX and connect to a different exchange's inward operator.

Although inward operators are not publicly dialable by ordinary NPSTN subscribers, they are a valuable service for our network operators. Please do not underestimate their importance. If you can designate a particular phone, extension, or person as an inward for your numberspace, please do it! It allows us to make sure that calls can get completed even when there are network difficulties, equipment failures, switch congestion, or trunking issues, etc.

Inward operator routing information is not publicly available, as only network operators have access to inward routing information. However, any exchange owner may (and is encouraged to) provide incoming facilities for operators.

IAX User

[npstn-operator] ; Incoming/outgoing operator routings (recommended)

Dialplan Code

This template can be modified to suit each node's needs:

[from-npstn-operator] ; Incoming calls to/from NPSTN operators
exten => s,1,Goto(000,1)
exten => _X!,1,Gosub(npstn-verify,${EXTEN},1)
	same => n,ExecIf($["${clidverif}"!="10"]?Hangup(21))
	same => n,Set(GROUP(npstnoperatortrunks)=0)
	same => n,ExecIf($[${GROUP_COUNT(0@npstnoperatortrunks)}>10]?Hangup(34))
	same => n,Goto(npstn-operator-route,${EXTEN},1)

exten => 000,1,Goto(s,1)
exten => _000.,1,Goto(local-operator-ring,${EXTEN:3},1) ; Network operator ring group
exten => _1[2346]1,1,Goto(local-operator-ring,${EXTEN},1) ; 121 = inward, 131 = information, 141 = rate/route, 161 = local repair
exten => s,1,PlayTones(2600)
	same => n,Wait(0.04) ; wink, to indicate ready to receive digits
	same => n,StopPlayTones()
	same => n,GotoIf($["${IAXVAR(optrunktype)}"="1"]?dtmf,1:mf,1)
exten => mf,1,ExecIf($[${DIALPLAN_EXISTS(mf-get-number,s,1)}]?Gosub(mf-get-number,s,1):Hangup(69)) ; receive additional digits if MF capable
	same => n,Goto(${GOSUB_RETVAL},1)
exten => dtmf,1,Set(TIMEOUT(response)=20)
	same => n,Read(digits) ; for now, read additional digits using DTMF: ideally, this is using MF on switches supporting it
	same => n,GotoIf($["${FILTER(0-9,${digits})}"=""]?invalid,1)
	same => n,Goto(${FILTER(0-9,${digits})},1)
exten => _NXX!,1,NoOp() ; NXX routings
	same => n,Set(ext=${EXTEN:3})
	same => n,ExecIf($["${ext}"=""]?Set(ext=s))
	same => n,Set(lookup=${SHELL(curl "${npstnkey}&code=${EXTEN:0:3}")})
	same => n,GotoIf($["${lookup}"=""|"${lookup}"="0"]?invalid,1)
	same => n,Gosub(npstn-out-verify,${EXTEN},1)
	same => n,Dial(${lookup}${ext})
	same => n,Goto(invalid,1)
exten => _NXX0XX!,1,NoOp() ; NXX0XX routings
	same => n,Set(ext=${EXTEN:6})
	same => n,ExecIf($["${ext}"=""]?Set(ext=s))
	same => n,Set(lookup=${SHELL(curl "${npstnkey}&code=${EXTEN:0:6}")})
	same => n,Gosub(npstn-out-verify,${EXTEN},1)
	same => n,GotoIf($["${lookup}"=""|"${lookup}"="0"]?invalid,1)
	same => n,Dial(${lookup}${ext})
	same => n,Goto(invalid,1)
exten => invalid,1,PlayTones(congestion)
	same => n,Wait(600)
	same => n,Hangup()
exten => _NNXXXXX,1,Goto(dtroute,${EXTEN},1)
include => local-operator-custom

exten => _X!,1,Goto(npstn-operator-route,invalid,1)
exten => 102,1,PlayTones(1004) ; 102 MW test
	same => n,Wait(600)
	same => n,Hangup()
; other local test lines (e.g. exten => 105 for 105-type test, etc.)

[local-operator-ring] ; 0/411/611/121/131/141/161/etc.
exten => _X!,1,Dial(SIP/operator,,th) ; change this to whatever endpoint(s) should ring local/inward operator calls on your switch
	same => n,Goto(npstn-operator-route,invalid,1)
; If desired, different endpoints can ring for different call types (e.g. 121, 131, 141, etc.)


NPSTN is a free unique telephone collectors' network. NPSTN has a "billing" functionality that is purely for fun. The idea is to show you all the calls you make on NPSTN and what they would have cost back in the day. The bill is for fun, and you are not actually charged anything. NPSTN is and always will be 100% free.

One of the most unique things about NPSTN is its state-of-the-art mock "billing" system. Nodes participate in this system voluntarily and with little overhead on their part. The code necessary to generate the necessary "toll tickets" is part of the basic boilerplate NPSTN code. Once a user has made a call from his or her telephone, he or she can login to the NPSTN User Control Panel. Once logged in, in the directory, you will see all numbers you have listed in both the general and residential directories.

If you have made any calls from any of the numbers listed here via a node participating in the billing system, your calls are automatically ticketed. A charge is issued for all completed calls based on the time and day when the call was made, the caller and the callee and the distance between them, and how the call was made. This means calls during the day will cost more than calls in the evening or on weekends, calls completed to NPSTN nodes further away will cost more than those to nearby nodes, and calls completed with the assistance of an operator will cost more than those completed without.

Note that all of these charges are just for show. You will never be issued a real bill demanding payment. This billing system exists exclusively for the entertainment and amusement of NPSTN callers, although it also functions as a historical complement. All of the rates on the network are based on those issued with the Bell System's 1977 issue of its "Phone Thing", which was distributed to many customers in the late 1970s. If you have one of the originals, it will accurately tell you the cost of all of your NPSTN calls! Haven't one? Fear not! Print out the document linked above and assemble your own helpful dialing aid. You should be able to accurately predict the rate you will be charged based on the information in the "Phone Thing".

For the most part, the "Phone Thing" will accurately provide rate information. Designed for interstate phone calls on the PSTN, it applies to NPSTN whenever "long distance" calls occur — that is, calls between a node and any other. A few exceptions exist to NPSTN's otherwise completely accurately simulated billing model exist: local calls are all charged a $0.05 fee and use 1 message unit in the process. In some locales, such as New York City, local calls were not free and used message units; therefore, this is not explicitly unrealistic. Furthermore, all "international calls" to nodes outside of the U.S., as well as calls that involve any international call, are billed at a rate higher than that assessed when placing a continental call of the maximum distance possible. Specifically, international calls are $1 for the first minute and $0.50 for each subsequent minute, if dialed directly during "peak" (full rate) hours. As the "Phone Thing" dictates, a 35% of 60% discount applies during select hours. Calls to special numbers that contain fewer than 7 digits (e.g. 0, 11N, N11, and 958/959, as well as 660+) are not ticketed and are thus completely free. Such calls will not appear on your bill.

The time used when issuing a charge for each call is determined by the local timezone of the switch from which a call originates. Therefore, it is possible members hosted on other members' nodes who are not located in the same time zone may experience discrepancies between their local time and the time used when determining the rate period for that call. Consequently, however, it is imperative that the time on all nodes are correct, and not merely left to the default (most likely UTC, which won't be helpful unless you are, actually, in UTC!).

Additionally, because callers' locations are used when calculating the rates for each call, all NPSTN users should have their own NPSTN UCP (User Control Panel) account, which they will use to add themselves to the directory. Node owners should not add hosted users on their node to any of the directories using their account. All users should have their own UCP account. If you don't have one, contact us and ask for an NPSTN UCP account to be created for you (currently, there is no way to create one yourselves). You will need to provide us with your full name, your ZIP code, your city, and your state. International users don't need to provide a ZIP code, obviously, but should let us know the country in which they are located, in addition to their city. We will email you your auth key, which is used to access the UCP (in lieu of a username and password - it goes without saying, don't share this with anyone else!). In order to view your bill, you should ensure that you've added yourself to the White Pages on NPSTN (residential listings) using your billing number (in other words, the number used for your caller ID on outgoing NPSTN calls). If you are hosted, this is likely set by your host. If not, then you as a node owner are responsible for ensuring the billing system is in place on your node. See the "Common Contexts" section for more on this. If you use our bare bones boilerplate code to get started, you should be all set! You don't need to do anything else to have a bill generated for you other than make some phone calls! Once you have a balance under a number, that number will appear hyperlinked in all of the directories in the UCP in which that number appears. Click on the link to open the bill for that number. Charges reflect all calls made during the last billing cycle, which — on NPSTN — is merely the last 30 days. After 30 days, charges will disappear from your bill and no longer will be reflected in your balance.

If you have multiple numbers used to make calls, a bill is generated for each number.

As stated before, this entire operation is purely for fun. You will never have to pay the balance that appears on your billing statements. However, the charges you see do accurately reflect the charges that would have been issued on any NPSTN calls you make had you made those same calls to the same people using the PSTN in the late 1970s. The rates reflect the value of the dollar during that era; therefore, accounting for inflation, their actual impact would be much greater. However, consider the charges on your billing statement to be an accurate reflection of the cost of your telephone calls — in the late 1970s!

NMTS (NPSTN Mobile Telephone Service)

The 973 exchange is the Mobile Telephone Service exchange on NPSTN (in the style of Bell System IMTS - Improved Mobile Telephone Service). Radiotelephones and mobiles are located in this exchange.

Contact the Business Office for IMTS service.

ZEnith Numbers

NPSTN has ZEnith numbers, the predecessor to toll-free numbers on the PSTN. Calls to ZEnith numbers are basically "automatic collect" calls. These calls must be placed through the operator. You cannot dial them yourself.

NPSTN eXtensible Dialplan

The eXtensible dialplan is a collection of dynamically updated dialplan files you can add to your node to ease maintenance and add additional cool functionality!

It is recommended that all NPSTN nodes include the eXtensible dialplan to get the most out of the network, but it is not explicitly required for all features. Some code libraries are so complex that they are not included as standalone versions in the Docs and are only presents in the eXtensible dialplan.

The minimum supported version of Asterisk for eXtensibility is version 13. Version 12 most likely will work fine but has not been tested; much older versions like 1.x versions are not likely to work.

Code included in the eXtensible dialplan is generally tested rigorously in production on several nodes before being included as part of the eXtensible dialplan, to ensure its robustness.

At this time, we are actively adding features to the eXtensible dialplan, so expect the list below to grow in the near future:

eXtensible dialplan features:

Coming soon to the eXtensible dialplan:

The eXtensible dialplan evolved as a way to seamlessly roll out new features to participating nodes as they were made available. The infrastructure also allows bugs to be fixed, if needed, without any effort on the part of the node owner, since code is seamlessly and securely patched. The latest version of code is always used, automatically (provided the node has an Internet connection, which we'll assume is true if it's receiving/sending calls from/to NPSTN).

The code also features "kill switch" references to disable certain features if desired.


DIG is required. You can install it by typing sudo apt-get install -y dnsutils on Debian.

The full suite of verification subroutines, while highly recommended, is not a pre-requisite. A small subset of the verification subroutines required for minimum operation of the eXtensible dialplan is included as part of it. It will not cause conflicts for nodes which already have the full suite of verification subroutines, nor will it cause problems with nodes that do not have them at all.


To load the eXtensible dialplan onto your node, all you need to is add: #include dialplan/npstnx0.conf to /etc/asterisk/extensions.conf. Then, save this file as npstnx0.conf in the directory /etc/asterisk/dialplan/npstnx0.conf:

; NPSTN eXtensible Dialplan File 0

; In iax.conf, add the following for use with this file and then reload iax2 (iax2 reload)
; [npstnextensible]
; type=user
; context=npstnextensible
; auth=md5
; secret=npstnextensible18771871984 ; do not change: this is not supposed to be a "secret". Call the Business Office if you have questions
; forceencryption=yes
; requirecalltoken=yes

exten => _*.,1,Goto(npstnextensible-route,${EXTEN},1)
exten => _X!,1,System(wget -P /etc/asterisk/dialplan/ --timestamping --no-cache)
	same => n,System(wget -P /etc/asterisk/dialplan/ --timestamping --no-cache)
	same => n,ExecIf($["${STAT(e,/etc/asterisk/dialplan/npstnx2.conf)}"="0"]?System(sudo touch /etc/asterisk/dialplan/npstnx2.conf)) ; just to make the Asterisk compiler happy, until we can download the file
	same => n,ExecIf($["${STAT(e,/etc/asterisk/dialplan/verification.conf)}"="0"]?System(sudo touch /etc/asterisk/dialplan/verification.conf)) ; ditto
	same => n,System(asterisk -rx "dialplan reload") ; Allows uses the latest, most up-to-date version
	same => n,Goto(npstnextensible-route,${EXTEN},1)

#include dialplan/npstnx1.conf ; eXtensible "essentials", operator verification, switch maintenance

As you can see, you'll also need to create a new IAX context, meaning you'll add exactly the following to iax.conf:

secret=npstnextensible18771871984 ; do not change: this is not supposed to be a "secret". Call the Business Office if you have questions

The directory locations and names must exactly match. If the dialplan folder does not exist, you must create it.

At the time of inclusion of this code into the Docs, the above is the latest version of npstnx0.conf — however, it doesn't really matter. This file will automatically update itself when you add it to your dialplan, so even if npstnx0.conf is updated later and not updated here, it doesn't matter, since you'll get the latest copy directly from the server anyways. Perhaps, you can begin to see just how cool this is already!

The underlying concept behind the eXtensible dialplan - namely dynamic dialplan updating - also is part of StepNet and other backend NPSTN systems, but the eXtensible dialplan was designed to fully leverage this functionality to provide a better experience for everyone. Furthermore, it's extremely secure, since all incoming calls are rejected unless they come from the NPSTN operator tandem, to which only network operators have access.

We won't go into a detailed overview of the code behind the eXtensible dialplan code, but all of the code for the eXtensible dialplan is entirely "open source", so feel free to examine it and contact the Business Office if you have questions.

Hint: If you have duplicate phone numbers for some of your lines, be sure to refer to the Number Aliases section below!

Number Aliases

If you have any "number aliases", that is, telephone numbers that map to the same physical line, you should define each of these aliases in sip.conf:

For example, say the following SIP peer was defined in the dialplan:

	defaultuser = ATAx1
	secret = P@ssw0rd
	authid = ATAx1
	callerid = "John Smith" <5551212>
	context = from-internal

Say the number 555-8372 also mapped to this line, for whatever reason. This should be defined in sip.conf as a comment exactly like so:

	defaultuser = ATAx1
	secret = P@ssw0rd
	authid = ATAx1
	callerid = "John Smith" <5551212> ; 5558372
	context = from-internal

The number is a comment, so it isn't actually interpreted by Asterisk, but it is interpreted by the eXtensible dialplan code, so this is very important!

If you have more than one alias, simply list them all on the same line, separating each with spaces and commas.

If you load verification.conf onto your switch and #include dialplan/verification.conf OR #include verification.conf, if your node is extensible, then the eXtensible dialplan will AUTOMATICALLY RELOAD the verification subroutines for if you if they are updated! As of December 2020, the eXtensible dialplan automatically includes the verification subroutines, so do not include the file yourself if your node is eXtensible.

Generally these updates are feature updates and improvements and new features, not security patches ☺. The improvements are "nice to have", but not 100% essential to the operation of the verification subroutines.

If your node is not extensible, your copy of the verification subroutines will not change and you will need to re-download them if improvements that you want incorporated on your node are made.

All extensible nodes will automatically have their verification subroutines updated as needed if/when changes are made, as long as the verification conf file is in one of the two specified locations (/etc/asterisk/dialplan/verification.conf OR /etc/asterisk/verification.conf). This includes nodes that are not initially extensible but become extensible later, although such nodes will need to remove their #include dialplan/verification.conf because the eXtensible dialplan automatically includes them.


NPSTN has a set of standards to which all members are highly encouraged to adhere. Doing so ensures the network operates reliably and smoothly for everyone with an enjoyable experience for all.

New functionality and standards are driven by new and changing network goals and requirements. Such standards are ultimately determined by administration, with input solicited from the community with Requests For Comments during planning periods. NPSTN community input is always welcome and encouraged. Administration will direct standards per network requirements on behalf of and in response to the NPSTN community.

Although this list is inevitably incomplete, here are a few important recommended NPSTN standards:

Standard Variables

NPSTN uses a number of standardized dialplan and IAX variables. Their names and purposes are described below:

ScopeDialplan variableIAX variableNamePurposeSet ConditionsValid Values
GlobalnpstnkeyNPSTN KeyContains tandem owner's NPSTN auth keyN/A32-char auth key
GlobalclliCLLIContains tandem's CLLI code for ticketing purposesN/ANPSTN* (~8-~11 char. alphanumeric code)
GlobalzipcodeZIP CodeContains tandem owner's ZIP codeN/AUS ZIP code or 00000
GlobalallowdisathruAllow DISA ThruOptionally prevents DISA calls from exiting nodeN/A0 or 1
GlobalallowpstnthruAllow PSTN ThruOptionally prevents PSTN calls from exiting nodeN/A0 or 1
GlobalusedefaultblacklistresultUse Default Blacklist ResultAutomatically mitigate spam or nuisance phone calls according to network recommendationsN/A0 or 1
GlobalmaindisaDISAVIAMain DISAContain's a 7-digit # routing to that nodeN/A7-digit NPSTN number on node
ChannelclidverifclidverifCaller Verification Status codeContains caller's originating network and verification statusVerification Subroutines2-digit CVS code (see Verification).
ChannelautovonprioritydigitnpstnmlppCall PriorityContains call precedenceMLPP/AUTOVON routinesA, B, C, D, 0
ChannelforcesecurechannelforcesecurechannelForce Secure ChannelMandates secure callingCalling Feature (e.g. VSC)0 or 1
Channelisup-oliisup-oliANI II DigitsOriginating Line InformationManually in dialplanSee ANI II.
ChanneloptrunktypeOperator Trunk TypeOperator Trunk TypeManually in dialplan0 for MF, 1 for DTMF

By their very nature, global variables are static.

Variables will be added as they are needed for network features. All channel variables listed above should be rewritten if calls are regenerated in order to maintain proper channel state (if you are not doing complicated things in your dialplan using ConfBridge(), you probably don't need to worry about this).

NPSTN Intercept Codes

NPSTN has a number of "NP Intercept" codes. These codes are integrated into the CRTC and are not provided by end offices or Class 4 tandems. (Thus, these codes should not be used by member nodes, but members may be interested in what these codes mean and what codes exist.)

These codes are manually dialable in the 231-90XX range.

50Emergency Intercept911 is dialed; no longer used due to Volunteer Emergency Dispatch
53Authentication FailureCRTC/Route table authentication failed, call redirected to intercept
54Verification FailureReverse-verification of call per verification subroutines failed
57Centralized InterceptNon-assigned number is dialed (number not in service)
58Permanent SignalNo number is dialed
61Number on Your Own Line RingbackSame number is dialed; avoidable by not performing lookups for local numbers or not providing caller's ANI
77Inclement WeatherDestination tandem is unavailable due to weather-related conditions
88Facility TroubleDestination tandem is unavailable due to non-local facility trouble
89Non-Provisioned NumberAssigned but unprovisioned number dialed
90Invalid CodeInvalid NP Code

Member nodes may take advantage of network centralized intercept services. To do so, perform a lookup for a non-assigned network number and note the format (57 + the dialed #). In the dialplan, adjust your pattern matching such that the dialed number is suffixed to the 57 extension prefix. If no number can be provided, 57 alone is sufficient.

ANI II Digits

ANI II (pronounced "A-N-I two or Annie two") digits provide information about the type of originating line, call routing, and class of service. They are used on the PSTN for a variety of purposes. On NPSTN, they provide additional information to downstream nodes about the nature of the call being received.

For the sake of consistency, the ANI II codes used on NPSTN mirror their PSTN counterparts where relevant or possible. However, the exact usage of many codes is slightly different.

00Normal subscriber line (implied)
01Multiparty line
02ANI failure
27Network controlled payphone
60TRS call
61IMTS (Improved Mobile Telephone Service)
70Non-network controlled payphone (e.g. COCOT, Charge-A-Call)

Numbering Plan

A few words about the NPSTN numbering plan:

In order to be a valid NPSTN number, a number must be an odd number of digits. This does not necessarily mean a number is valid if its digit length is odd! All valid numbers on NPSTN are either 1, 3, 5, 7, or 8 digits long.

For the most part, NPSTN adheres to the traditional NANPA guidelines as they were originally set. This means that although NXX office codes exist today in the PSTN in the U.S., only NNX office codes are allowed on NPSTN.

Furthermore, unlike C*NET, NPSTN does not use country codes. Any node anywhere in the world on the network can be dialed with just 7 digits. This was an intentional decision reached by the NPSTN Board of Directors in order to make all nodes seem "local" and not "far away". Dialing more than 7 digits can discourage others from dialing a destination. This was a decision reached to better the network's experience for everyone.

A feature added later is the ability to dial a 1+ long-distance call. Dialing 1 + 7 digits will route your call over 2600 Hz-controlled trunks to the destination. The call goes to the same destination as if you had dialed it using 7 digits, but using a 2600-Hz controlled trunk, so now you can use your blue box on the call if you like.

In addition to that, NPSTN has Feature Group B (950-WXXX) and Feature Group D dial-around codes (101XXXX). At this time, a public listing of these carrier access codes is not available.

Note that 10[02-9]XX represents former Feature Group D codes. These codes are part of the NPSTN dial plan and will be routed to the appropriate intercept.

976 exchange numbers are premium-rate numbers charged at a premium rate.

Furthermore, there are special numbers on NPSTN, such as the following operator and directory services:

  • 0 — Operator — rings human operator, goes to automatic operator if unavailable
  • 411 — Directory Assistance — rings human operator, goes to automatic operator if unavailable
  • KL5-1212 — same as 411, KLondike5-1212 (555-1212) will ring Directory Assistance
  • 611 — Repair Service — rings human operator, goes to automatic operator if unavailable
  • 711 — TTY Relay
  • 811 — Business Office — rings the business office
  • 112 — "NPSTN Long Distance Operator" — rings automatic operator directly
  • 113 — Information — rings automatic information operator directly
  • 114 — Repair Service — rings automatic repair agent directly
  • 117 — Telegrams
  • 118 — StepNet Trunk
  • 119 — Long Distance Trunk (Stacking)

811 is not currently assigned but may be used in the future.

Other directory services include:

  • POPCORN — POPCORN, a.k.a. 767-2676 will provide your local time
  • 211 — Time & Temperature
  • 511 — Temperature & Location
  • 911 — Emergency Intercept Recording

Finally, network-wide test numbers include:

  • 311 — "Party Line"
  • 660 — Ring & Tone Test
  • 958 — ANAC
  • 959 — Ringback

To avoid confusion, we will use "Directory Assistance" to refer to 411 and 555-1212, which go first to a human operator and then to an automatic one if none is available and "Information" to refer exclusively to 113, which is our automated directory service.

Other special NXX/NXX codes include 101 (Feature Group D), 950 (Feature Group B), 976 (premium rate), 973 (mobile telephone service), and 555 (which is not reserved, in full or part, for fictitious numbers, as it is on the PSTN).

The following presents a concise dialplan version of the NPSTN numbering plan:

exten => 0,1,GoTo(npstn,${EXTEN},1)
exten => _11N,1,GoTo(npstn,${EXTEN},1)
exten => _N11,1,GoTo(npstn,${EXTEN},1)
exten => 660,1,GoTo(npstn,${EXTEN},1)
exten => _95[89],1,GoTo(npstn,${EXTEN},1)
exten => _10[02-9]XX,1,GoTo(npstn,${EXTEN},1)
exten => _101XXXX,1,GoTo(npstn,${EXTEN},1)
exten => _NNXXXXX,1,GoTo(npstn,${EXTEN},1) ; you can also use NXXXXXX, calls that are NXXXXXX but not NNXXXXX will be intercepted
exten => _1NNXXXXX,1,GoTo(npstn,${EXTEN},1)

exten => _X!,1,GoSub(dialnpstn,start,1(${EXTEN}))
	same => n,Hangup()

Automatic Call Distributor Hack

Calls to 555-1212 route over a specially emulated "#5XB automatic call distributor" trunk. It works the way that Bill Acker described in 2014. You'll need a silver box, such as that found at PhreakNet in order to drop into maintenance mode on this trunk.

Case Study: NPSTNNA01

The 3-digit codes above form the basic exceptions to 7-digit dialing on NPSTN that you will need to handle. Of course, many exchanges have even more complex routing. Below is the dial plan for NPSTNNA01 (NPSTN North America 1). You can try the following out for yourself by dialing into one of its DISAs (e.g. BE1-1111).

Below, N indicates a digit from 2 through 9, X indicates any digit from 0 to 9, W indicates either a 0 or 1, and Z indicates any digit except for 1. Our use of the W here is based off of its use in Notes on the Network, Signaling → LATA Access. Z was used in same capacity as X in Notes in the same section, but we are choosing to use it as Asterisk uses it (any digit from 1 through 9). We have defined U to arbitrarily mean 0 or 2 through 9.

X = [0-9], N = [2-9], W = [01], Z = [1-9], U = [02-9]

  • "" → permanent signal intercept
  • "*" → Momentary 2600 Hz Supervision
  • "0" → Operator
  • "N0" → Tie Lines
  • "11N" → 11N codes
    • "112" → Long Distance Operator
    • "113" → Information
    • "114" → Repair
    • "115" → Long Distance (C*NET)
    • "116" → Business Office
    • "117" → Telegrams
    • "118" → StepNet Trunk
    • "119" → Long Distance Trunk
  • "N1N" → Vacant (Switch Level Busy)
  • "310" → Tie Line to SxS PBX
  • "N11" → N11 codes
    • "211" → Time & Temperature
    • "311" → "Party Line" Conference
    • "411" → Directory Assistance
    • "511" → Temperature & Location
    • "611" → Repair
    • "711" → TTY Relay
    • "811" → Business Office
    • "911" → Emergency
  • "660" → Ring & Tone Test
  • "958" → ANAC
  • "959" → Ringback
  • "10UXX" → Feature Group D CAC Changed Intercept
  • "5551212" → Directory Assistance
  • "7365000" → PEnnsylvania6-5000
  • "7672676" → POPCORN (Time)
  • "8675309" → Jenny/867-5309
  • "NNXXXXX" → Local (Intra-Office)
    • "231XXXX" → BEechwood1
    • "234XXXX" → BEechwood4
    • "251XXXX" → CLearbrook1
    • "288XXXX" → BUtterfield8
    • "355XXXX" → FLeetwood5
    • "444XXXX" → HIckory4
    • "549XXXX" → LIncoln9
    • "650XXXX" → OLdfield0
    • "747XXXX" → PIoneer7
  • "101XXXX" → Feature Group D Dial-Around
  • "950WXXX" → Feature Group B Dial-Around
  • "976XXXX" → Premium Rate
  • "NNXXXXX" → Long-Distance (NNX-XXXX)
  • "1NNXXXXX" → Long-Distance (NNX-XXXX) via 2600 Hz trunk
  • "1NWXXXXX" → 0xx/1xx Intercept

Standard Numbers

The following is a list of standardized station numbers (the last 4 digits of NNX-XXXX) on NPSTN. If you use any of the following, you are highly encouraged to adhere to the following numbering conventions, assuming the "1" and "9" thousand blocks are allocated to you (if not, you may wish to consider allocating X9xx extensions as such instead). Being in compliance ensures that all users can expect a consistent experience across most exchanges. If your node is not in compliance, it is especially important any numbers similar to the following are published in the directory. If your node is in compliance, it is up to you whether you feel it necessary to publish these extensions in the network directory.

  • "0041" → Loop Around Side A
  • "0042" → Loop Around Side B
  • "1111" → Switch DISA
  • "9900" → Milliwatt
  • "9901" → Switch Verification
  • "9906" → Milliwatt Synchronization
  • "9922" → DTMF Test
  • "9931" → Echo Test
  • "9932" → Silent Termination
  • "9941" → Switch Telephone
  • "9945" → Milliwatt
  • "9950" → Business Office
  • "9960" → Milliwatt
  • "9970" → Busy
  • "9971" → Reorder
  • "9972" → Supervision Test
  • "9979" → Tone Sweep (Loop Checker/Loop Check Generator)
  • "9990" → Ringout
  • "9996" → Loop Around Side A
  • "9997" → Loop Around Side B

The 99XX numbers here are called "Official" numbers. For example, 9901 is "Official 01". The "Official" numbers you see above are factually accurate and reflect Evan Doorbell's experiences in the greater New York area. The only non-"Official" number above is 1111, which is purely an NPSTN convention. NNX-9941 is also a modern, rather than historical, convention. While NNX-9990 was used historically as the number to reach the switch telephone, since 9941 is already reserved for that purpose, 9990 is generally reserved for ringout tests (i.e. ring, no answer). While NNX-9901 used to commonly go to an actual verification operator, today the number usually provides basic switch information; nevertheless, the name "switch verification" has stuck.

Since echo tests and DTMF tests were not common in the old network, we have settled on sensible numbers here which have no historical basis.


PSTN DID Exchanges

The thousands blocks (406) 957-1XXX on the PSTN is direct inward dial** into NPSTN.

PSTN DID numbers route into NPSTN through NPSTNNA01.

Qualified NPSTN members may qualify for a DID number. Contact NPSTN for more details. You may dial 811 during business hours to reach the NPSTN business office.

PSTN DID Allocation

Some numbers in the 957 ranges are "missing":

The following station numbers from 957-1 are missing:

  1. 1000
  2. 1001
  3. 1002

Vintage Add-Ons

The following are useful supplements to your Asterisk system:

Pat Fleet Asterisk Sounds

To replace Asterisk's default Allison Smith voice prompts with "vintage" Pat Fleet prompts, run the following:

cd /var/lib/asterisk/sounds/en
rm -f *
rm -rf dictate
rm -rf digits
rm -rf followme
rm -rf letters
rm -rf phonetic
rm -rf silence
cd /
unzip -d /var/lib/asterisk/sounds/en
rm -f

If the code above doesn't work for any reason, just know that the files and folders in /var/lib/asterisk/sounds/en should be replaced with those in the ZIP file, with the exception of the custom subdirectory which contains your custom recordings!

Automatic Intercept System

  1. If you don't already have the Jane Barbe spliced AIS recordings, download them all from the C*NET Sounds Download page. You only need the files in the sounds folder itself, not any of its subfolders. Furthermore, you need only download the .wav files (not the .tgz files, for instance).
  2. Use SoX to convert all of the wav files to ulaw files (see the SoX section in Appendix A for more on this).
  3. Move all of the audio files beginning with a numeral (a number followed by either an n or a d to /var/lib/asterisk/sounds/en/custom/digits/. You may need to create this directory.
  4. Move the remaining files to /var/lib/asterisk/sounds/en/custom/ais/. You may need to create this directory.
  5. Add the following to your extensions.conf:
    [ais7] ; NPSTN 20190212 NA
    exten => start,1,Set(num=${ARG1})
    	same => n,Playback(custom/digits/${num:-7:1}n,noanswer)
    	same => n,Playback(custom/digits/${num:-6:1}n,noanswer)
    	same => n,Playback(custom/digits/${num:-5:1}d,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n,Playback(custom/digits/${num:-4:1}n,noanswer)
    	same => n,GotoIf($[${num:-3:3}=000]?thousand)
    	same => n,Playback(custom/digits/${num:-3:1}n,noanswer)
    	same => n,Playback(custom/digits/${num:-2:1}n,noanswer)
    	same => n,Playback(custom/digits/${num:-1:1}d,noanswer)
    	same => n,Return()
    	same => n(thousand),Playback(custom/ais/thousand,noanswer)
    	same => n,Return()
    [aisnis] ; NPSTN 20190212 NA
    exten => start,1,Set(num=${ARG1})
    	same => n,Verbose(AIS Not In Service -- "${num}")
    	same => n,Playback(custom/ais/clunk,noanswer)
    	same => n,Playback(custom/ais/numreach,noanswer)
    	same => n,GoSub(ais7,start,1(${num}))
    	same => n,Playback(custom/ais/notinservice,noanswer)
    	same => n,Playback(custom/ais/pleasecheck,noanswer)
    	same => n,Playback(custom/ais/dialagain,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n,Return
    [aischanged] ; NPSTN 20190212 NA ; This requires an argument with the new number
    exten => start,1,Set(num=${ARG1})
    	same => n,Set(newnum=${ARG2})
    	same => n,Verbose(AIS Number Changed -- "${num}")
    	same => n,Playback(custom/ais/clunk,noanswer)
    	same => n,Playback(custom/ais/numreach,noanswer)
    	same => n,GoSub(ais7,start,1(${num}))
    	same => n,Playback(custom/ais/hasbeenchanged,noanswer)
    	same => n,Playback(custom/ais/newnumberis,noanswer)
    	same => n,GoSub(ais7,start,1(${newnum}))
    	same => n,Playback(custom/ais/pleasemakenote,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n,Return
    [aisnpachanged] ; NPSTN 20190212 NA ; This requires an argument with the new number
    exten => start,1,Set(oldnum=${ARG1})
    	same => n,Set(newnum=${ARG2})
    	same => n,Verbose(AIS Number Changed To Different NPA -- "${oldnum}")
    	same => n,Playback(custom/ais/clunk,noanswer)
    	same => n,Playback(custom/ais/numreach,noanswer)
    	same => n,GoToIf($[${oldnum}=nonum]?changed)
    	same => n,GoSub(ais7,start,1(${oldnum}))
    	same => n(changed),Playback(custom/ais/hasbeenchanged,noanswer)
    	same => n,Playback(custom/ais/newnumberis,noanswer)
    	same => n,GoToIf($[${LEN(${newnum})}=7]?seven)
    	same => n,Playback(custom/ais/area,noanswer)
    	same => n,Playback(custom/digits/${newnum:-10:1}n,noanswer)
    	same => n,Playback(custom/digits/${newnum:-9:1}n,noanswer)
    	same => n,Playback(custom/digits/${newnum:-8:1}d,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n(seven),GoSub(ais7,start,1(${newnum}))
    	same => n,Playback(custom/ais/pleasemakenote,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n,Playback(custom/ais/numreach,noanswer)
    	same => n,Playback(custom/ais/hasbeenchanged,noanswer)
    	same => n,Playback(custom/ais/newnumberis,noanswer)
    	same => n,GoToIf($[${LEN(${newnum})}=7]?seven2)
    	same => n,Playback(custom/ais/area,noanswer)
    	same => n,Playback(custom/digits/${newnum:-10:1}n,noanswer)
    	same => n,Playback(custom/digits/${newnum:-9:1}n,noanswer)
    	same => n,Playback(custom/digits/${newnum:-8:1}d,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n(seven2),GoSub(ais7,start,1(${newnum}))
    	same => n,Playback(custom/ais/pleasemakenote,noanswer)
    	same => n,Playback(custom/ais/dialagain,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n,Return
    [aisworking] ; NPSTN 20190212 NA
    exten => start,1,Set(num=${ARG1})
    	same => n,Verbose(AIS Working Number -- "${num}")
    	same => n,Playback(custom/ais/clunk,noanswer)
    	same => n,Playback(custom/ais/numreach,noanswer)
    	same => n,GoSub(ais7,start,1(${num}))
    	same => n,Playback(custom/ais/isworkingnum,noanswer)
    	same => n,Playback(custom/ais/willdialagainplease,noanswer)
    	same => n,Playback(custom/digits/blank,noanswer)
    	same => n,Return
  6. Now, going back to our starter dialplan code, let's modify it so that AIS is used on all calls to nonexistent extensions. Here is what we had initially:
    … all your 555 extensions here …
    include => invalidincoming
    Now, tweak your code so it is as follows:
    … all your 555 extensions here …
    include => KLondike5-unmatched
    exten => _XXXX,1,GoSub(aisnis,start,1(${OC1}${EXTEN:-4}))
    	same => n,Hangup()

Now, any time anyone calls a 555-XXXX number that doesn't exist, he will hear AIS instead of CBCAD!

An important technical footnote is necessary here: if you have multiple office codes, you will need a separate AIS "catch" context for each of them. The reason for this is that by the time a call hits [KLondike5], only the last 4 digits have been sent to that context, meaning the [KLondike5] context has no idea what office code was dialed. However, AIS expects the proper office code! Notice that ${OC1} is specified to add the office code back in before sending the call to AIS.

Consequently, if you had both the KLondike5 and KLondike6 exchanges, your code might look like this:

… all your 555 extensions here …
include => KLondike5-unmatched

exten => _XXXX,1,GoSub(aisnis,start,1(${OC1}${EXTEN:-4}))
	same => n,Hangup()

… all your 556 extensions here …
include => KLondike6-unmatched

exten => _XXXX,1,GoSub(aisnis,start,1(${OC2}${EXTEN:-4}))
	same => n,Hangup()


A prerequisite for creating an ANAC (automatic number announcement circuit) is having the Jane Barbe AIS audio files downloaded. If you already have AIS on your system, you're all set. If you don't, you don't need AIS to have an ANAC. Simply navigate to the C*NET Sounds Download page and download all the files in that directory beginning with a numeral (you will see two for each number, one ending with an "n", and the other ending with a "d"). You don't need the other audio files in this directory unless you'd also like to set AIS up on your node.

Download all these files and use SoX to convert them to ulaw files (see Appendix A for more on this). Then, place the ulaw files inside /var/lib/asterisk/sounds/en/custom/digits/ on your system. You may need to create the digits directory if you don't have AIS on your node (and hence never created that directory).

Then, copy and paste the following code into your dialplan (i.e. extensions.conf):

[anac] ; NPSTN 20190212 NA
exten => start,1,Set(num=${CALLERID(num)})
	same => n,Verbose(ANAC -- "${num}")
	same => n,Set(num=${FILTER(0-9,${num})})
	same => n,GotoIf($[${LEN(${num})}=7]?d7)
	same => n,GotoIf($[${LEN(${num})}=6]?d6)
	same => n,GotoIf($[${LEN(${num})}=5]?d5)
	same => n,GotoIf($[${LEN(${num})}=4]?d4)
	same => n,GotoIf($[${LEN(${num})}=3]?d3)
	same => n,GotoIf($[${LEN(${num})}=2]?d2)
	same => n,GotoIf($[${LEN(${num})}=1]?d1)
	same => n,GotoIf($[${LEN(${num})}=0]?d0)
	same => n(d7),Playback(custom/digits/${num:-7:1}n,noanswer)
	same => n(d6),Playback(custom/digits/${num:-6:1}n,noanswer)
	same => n(d5),Playback(custom/digits/${num:-5:1}d,noanswer)
	same => n,Playback(custom/digits/blank,noanswer)
	same => n(d4),Playback(custom/digits/${num:-4:1}n,noanswer)
	same => n(d3),Playback(custom/digits/${num:-3:1}n,noanswer)
	same => n(d2),Playback(custom/digits/${num:-2:1}n,noanswer)
	same => n(d1),Playback(custom/digits/${num:-1:1}d,noanswer)
	same => n(d0),Playback(beep,noanswer)
	same => n(done),Return

Now, to make your ANAC work, you'll need to put it on an extension. "958" network-wide on NPSTN is an ANAC (the very same one you see here), so you may want to put your ANAC on an extension ending in 958, like so:

exten => 9958,1,GoSub(anac,start,1)
	same => n,Hangup()

The reason we haven't included an Answer() statement here is because this is a test line, and test lines in the old network hardly ever supervised. You can choose to have your ANAC supervise if you wish — NPSTN and C*NET callers are never "charged" for making calls, so unless you call this inbound from the PSTN from a payphone or home phone without flat-rate long-distance, it's really a non-issue — but to be "time-period correct", we've opted here to not supervise.

Speaking Clock

Doing time announcements is something relatively complex that Asterisk makes relatively simple. The code featured here is the same code that runs POPCORN network-wide. You can set-up a speaking clock or "time number" on your node that is hardcoded to use your timezone, if you wish. The following code makes this relatively easy to do:

[time] ; NPSTN 20190212 NA ; ARG1 is number of times to announce the time, ARG2 is the timezone to use
exten => s,1,Set(NumLoops=0)
	same => n(start),Set(FutureTime=$[${EPOCH} + 8])
	same => n,Set(FutureTimeMod=$[${FutureTime} % 10])
	same => n,Set(FutureTime=$[${FutureTime} - ${FutureTimeMod} + 10])
	same => n,Gosub(sub-hr12format,s,1(${ARG2}))
	same => n(waitloop),Set(TimeLeft=$[${FutureTime} - ${EPOCH}])
	same => n,GotoIf($[${TimeLeft} < 1]?playbeep)
	same => n,Wait(1)
	same => n,Goto(waitloop)
	same => n(playbeep),Playback(beep)
	same => n,GotoIf($[${LEN(${ARG4})}=6]?skipwait)
	same => n,Wait(5)
	same => n(skipwait),Set(NumLoops=$[${NumLoops} + 1])
	same => n,GotoIf($[${NumLoops} < ${ARG1}]?start)
	same => n,GotoIf($[${LEN(${ARG3})}=4]?skipgoodbye)
	same => n,Playback(goodbye)
	same => n(skipgoodbye),Return()

exten => s,1,Answer
exten => s,n,Playback(at-tone-time-exactly)
exten => s,n,SayUnixTime(${FutureTime},${ARG1},IM 'vm-and' S 'seconds' p)
exten => s,n,Return()
exten => s,n,Playback(at-tone-time-exactly)
exten => s,n,SayUnixTime(${FutureTime},${ARG1},IM 'vm-and' S 'seconds' p)
exten => s,n,Return()

ARG1 is the number of times to announce the time. POPCORN is set to announce the time 100 times, so it goes on for quite a while. You'll probably want to set yours to a number between 5 and 10.

ARG2 is the timezone to use. The timezones are the "tz" format used in Unix. You can use this list of timezones and corresponding TZ database names to help you.

ARG3 and ARG4 are optional, and you will probably not need to use them. If ARG3 is set to skip, or any 4 characters for that matter, the speaking clock will not say "goodbye" before exiting. You may need to use ARG3 if you use this speaking clock as part of a broader context, rather than just a single, dedicated extension.

If ARG4 is set to nowait, or any 6 characters for that matter, then the speaking clock will not wait 5 seconds after the beep before entering another cycle. This is usually useful when you are calling [time] with ARG1 set to 1 (i.e. you only want the time announced once), and you wish to immediately exit the subroutine after the beep and continue with the dialplan code in the extension that called it. Note that if ARG1 is NOT 1 and you use ARG4 to disable the wait, it will not only disable the wait the last cycle, but every cycle, which is potentially undesirable. ARG4 is only meant to be set if ARG1 is equal to 1, but if you want to make the subroutine a bit more foolproof, you might modify it slightly so that it only calls GotoIf($[${LEN(${ARG4})}=6]?skipwait) if it is in its last cycle (in which case NumLoops will be 1 less than ARG1).

Now, to create a speaking clock for say, Chicago, or any city located in the Central Time Zone, that announces the time 7 times, all you need to do is add the following to your dialplan:

exten => 2006,1,Answer() same => n,GoSub(time,s,1(7,America/Chicago)) same => n,Hangup()

The speaking clock above is the traditional "at the tone, the time will be exactly X:YY and ZZ seconds, A.M. … beep.

If you'd like something a bit more customizable, check out the Asterisk TIM project on GitHub. At this time, the voice provided is Pat Simmons, but you can obtain and use your own custom audio prompts.

Airport Weather

For instructions on how to get airport-code-based weather reports in Asterisk, see this NerdVittles tutorial.

Currently, we don't have any of these types of services on NPSTN. The temperature readings provided at BE1-1200 and related numbers are powered by paid APIs.

Milliwatt Test Tone

In Asterisk, there are actually several ways you could implement a milliwatt (or 1004 Hz test) tone. Asterisk itself has a Milliwatt function:

exten => start,1,Wait(0.5)
	same => n,Milliwatt(${ARG1})
	same => n,Return

ARG1 is the number of seconds for which to play the test tone. Usually, 10 minutes (or 600 seconds) will suffice. We wait 0.5 seconds before starting as milliwatt numbers usually offer a split second of silence before blasting your eardrums.

By now, you probably know that to call this, you would need to call GoSub(mw,start,1(600)) from an extension in your dialplan. (You can tell this is a subroutine because it has the Return statement in it.)

However, a great many people choose to create their milliwatt test numbers themselves. Since a milliwatt test tone consists of pure frequencies, this is relatively easy. To make things interesting, we have here chosen to combine 1004 Hz and 1000 Hz for our milliwatt (the latter was the original frequency, according to Steph Kerman "A mW is inherently a pure tone, originally nominally 1000Hz but eventually these tones were offset by 4Hz to 404, 1004 and 2804 for compatibility with digital transmission"):

[mwtone] ; NPSTN 2018 DC/NA
exten => start,1,Wait(0.5)
	same => n,PlayTones(1004/1000)
	same => n,Wait(${ARG1})
	same => n,Return

In this case, calling GoSub(mwtone,start,1(600)) would play a 1004 Hz tone for 600 seconds (or 10 minutes).

Now, if you want to really kick it up a notch, you can increase the volume of the channel so the milliwatt tone comes out more loudly than it would otherwise:

[mwtone] ; NPSTN 2018 DC/NA
exten => start,1,SET(VOLUME(TX)=8)
	same => n,Wait(0.5)
	same => n,PlayTones(1004/1000)
	same => n,Wait(${ARG1})
	same => n,SET(VOLUME(TX)=1)
	same => n,Return

A volume specification of about 8 matches PSTN milliwatt test numbers fairly closely. Of course, you can play around with this until you find a volume setting that works for you. Simply replace 8 with ${ARG2}, and then pass in a second argument containing the new volume. This way, you can easily create multiple extensions with different volume milliwatt tones.


Ringback is a fairly simple task that is quite involved in terms of dialplan code. Here is the code that operates the 959 ringback number on NPSTN (at least, it's some of the code; the 959 code is more involved as it will also ring back C*NET callers in addition to NPSTN callers):

[ringbacknpstn] ; NPSTN 2018 DC/NA
exten => s,1,Answer
	same => n,PlayTones(600*120)
	same => n,Wait(60)
exten => h,1,Verbose(Ringing Back NPSTN Caller ${CALLERID(num)})
	same => n,Set(lookup=${SHELL(curl "${npstnkey}lookup=${CALLERID(num)}")})
	same => n,Log(NOTICE, NPSTN Ringback: ${lookup})
	same => n,System(echo Channel: ${lookup} >> /tmp/cf.tmp)
	same => n,System(echo Application: Wait >> /tmp/cf.tmp)
	same => n,System(echo Data: 1209600 >> /tmp/cf.tmp)
	same => n,System(echo CallerID: "Ringback <${OC1}9959>" >> /tmp/cf.tmp)
	same => n,System(mv /tmp/cf.tmp /var/spool/asterisk/outgoing/)
	same => n,Hangup

The PlayTones(600*120) is what will happen when this number is called. In this case, you will here 600 Hz modulated with 120 Hz, or old city dial tone (though it sounds a bit modern, given that it's generated by a computer rather than an electromechanical tone plant). This line is optional, but does provide an indication to the caller that something has happened; if you don't want the caller to hear anything, you could replace this with Wait(600. Another option is choosing to play a NOYOL (number on your own line) announcement, which was used frequently when party lines were more common. You could then add some dialplan code in your exchange context to call [ringbacknpstn] whenever ${CALLERID(num)} is the same as ${OC1}${EXTEN}. This would allow you to form a simple intercom by allowing callers to dial their own number and hang up, which would allow multiple people to pick up and converse, even with just a single line.

The ringback itself doesn't actually happen until the caller hangs up (which is why the h extension is used here as well). When the caller hangs up, Asterisk creates a call file, dumps it in the spool directory, and it gets processed.

You will need to replace ${OC1}9959 here with the extension at which you create your ringback number. If you choose to have it be extension 9959 on your exchange, you don't need to change anything; otherwise, adjust the last 4 digits to the proper extension number. You should make sure that the number you use here matches the number used to call your ringback line. That way, if somebody calls your ringback line, the incoming Caller ID on the automatically generated call matches the number he dialed in the first place.

Note that [ringbacknpstn] is not a subroutine. You will need to call it as exten => 9959,1,GoTo(ringbacknpstn,s,1).

Revertive Pulsing Script

First, as outlined in the "Pre-Requisites" section, you will need to have PHP installed in order to use the revertive pulsing script. Once PHP is installed, exit the Asterisk CLI to the main command line and run the following commands:

mv pulsar-agi.tar.gz /var/lib/asterisk
cd /var/lib/asterisk
tar xvfz pulsar-agi.tar.gz
chmod 777 /var/lib/asterisk/agi-bin/pulsar.agi

A file named pulsar.agi should now be located in /var/lib/asterisk/agi-bin/.

Audio files go in /var/lib/asterisk/sounds/pulsar/.

It's worth mentioning that by default this script DOES supervise, which is incorrect! If you modify the AGI file, you can add the noanswer flag to the Playback command so that revertive pulsing does NOT return premature answer supervision (e.g. turn Playback(file) into Playback(file,noanswer).

Do not use revertive pulsing on NPSTN if your revertive pulsing supervises (i.e. don't use revertive pulsing if you are not going to modify the AGI). Otherwise, it will supervise, which is against the NPSTN supervision standards.

Now, add the following subroutine to your dialplan:

[revertive] ; NPSTN 20190212 NA ; Revertive pulsing (requires PHP installation)
exten => start,1,Verbose(Revertive Pulse Generator: ${ARG1})
	same => n,AGI(pulsar.agi,${ARG1},${ARG2},${ARG3}) ; ARG2 can be set to 1 to indicate a "B-side" that adds 5 pulses to the second RP digit.
	same => n,Return() ; ARG1 should be a 4-digit extension and ARG3 must be one of the following: panel,1xb,5xb

ARG1 is the 4-digit extension to revertive pulse.

ARG2 can be set to 1 to indicate a "B-side" that adds 5 pulses to the second RP digit. Otherwise, set it to 0.

ARG3 should be the type of revertive pulsing to simulate. Your options are panel, 1xb, or 5xb. If you are simulating one of these switches by chance, then use that one. Each type of revertive pulsing sounds very distinctive, so make sure to get this one right!

As you can see, you might call the revertive pulser like this: GoSub(revertive,start,1(${EXTEN:-4},0,5xb)). This will simulate #5XB revertive pulsing.

Unlike MF digits, which you could use for both inpulsing and outpulsing, revertive pulsing is a signaling method you should only use for incoming calls. Let's revisit our incoming contexts:

include => local
include => long-distance
include => invalidincoming

include => local
include => invalidincoming

exten => _${OC1}XXXX,1,GoTo(KLondike5,${EXTEN:-4:4},1)

You might modify [local] so it is as follows:

exten => _${OC1}XXXX,1,GoSub(revertive,start,1(${EXTEN:-4},0,5xb))
	same => n,GoTo(KLondike5,${EXTEN:-4:4},1)

Now, all callers dialing into whichever office code ${OC1} happens to be will hear Number 5 crossbar revertive inpulsing before the call connects!

The only catch here is, if you look at the [internal-users] context, you will see that you will also hear revertive pulsing on a call that is "local" to you, i.e. within the same exchange. At worst, this is undesired, at best, it is not really technically correct.

The solution is having a different context for incoming "long-distance" calls, like so:

include => local-intraoffice
include => long-distance
include => invalidincoming

include => local-interoffice
include => invalidincoming

exten => _${OC1}XXXX,1,GoTo(KLondike5,${EXTEN:-4:4},1)

exten => _${OC1}XXXX,1,GoSub(revertive,start,1(${EXTEN:-4},0,5xb))
	same => n,GoTo(KLondike5,${EXTEN:-4:4},1)

Now, callers from other exchanges will hear revertive pulsing on all calls to KLondike5, but you will not. It is recommended that you adopt this approach.

If you'd like, in order to economize on code, you might adjust the last two contexts to the following:

exten => _${OC1}XXXX,1,GoTo(KLondike5,${EXTEN:-4:4},1)

exten => _${OC1}XXXX,1,GoSub(revertive,start,1(${EXTEN:-4},0,5xb))
	same => n,GoTo(local-intraoffice,${EXTEN:-4:4},1)

What's the point of this, you might ask? We just made external callers hop through another step!

The advantage of structuring your code this way is you can simply define the inpulsing for each exchange in [local-interoffice], then send it to the other context. If you wanted a sound played on all incoming calls, you could include it in the first context only; in other words, you can cut down on future duplicated code.

The downside of using this approach is, as before, all internal and external callers are now sent to the same destination context. If you had a [KLondike5-internal] context, you might want to keep them separate, in order to allow different classes of callers access to different classes of extensions:

[internal-users] include => local-intraoffice include => long-distance include => invalidincoming [external-users] include => local-interoffice include => invalidincoming [local-intraoffice] exten => _${OC1}XXXX,1,GoTo(KLondike5-internal,${EXTEN:-4:4},1) [local-interoffice] exten => _${OC1}XXXX,1,GoSub(revertive,start,1(${EXTEN:-4},0,5xb)) same => n,GoTo(KLondike5,${EXTEN:-4:4},1) [KLondike5-internal] exten => 6666,1,GoTo(verysecretcontext,s,1) include => KLondike5 [KLondike5] ; all public extensions here include => invalidincoming ; if you have AIS, you should include KLondike5-unmatched instead

Now, if you call KL5-6666, you will end up going to [verysecretcontext], but callers from other exchanges will end up hearing an intercept message (or AIS) instead. Technically, this might not make sense, as the extension exists; it's just not accessible to external callers, and from Asterisk's perspective when routing an incoming call from another exchanges, the extension does not exist since it's in a different context to which it does not have access.

Part of Asterisk is structuring your dialplan code properly, and this is a skill that takes time to develop. Fortunately, you have an almost infinite amount of flexibility in terms of how you route your calls, so pick the approach that works best for you, knowing that you have the flexibility to tweak it should you ever need to.


Perhaps one of the most common and popular additions to any node is the MFer. Creating an MFer that would properly work with any number of digits proved to be a challenge, but in the end, it was surmounted. First, you may want to consider adding the MF tone specifications to the [us] context (if you are in the U.S.) in indications.conf, even though we will not be using them expressly:

mf1 = !700+900/55,!0/50
mf2 = !700+1100/55,!0/50
mf3 = !900+1100/55,!0/50
mf4 = !700+1300/55,!0/50
mf5 = !900+1300/55,!0/50
mf6 = !1100+1300/55,!0/50
mf7 = !700+1500/55,!0/50
mf8 = !900+1500/55,!0/50
mf9 = !1100+1500/55,!0/50
mf0 = !1300+1500/55,!0/50
kp = !1100+1700/100,!0/50
kp2 = !1300+1700/100,!0/50
st = !1500+1700/35,!0/50
st2 = !900+1700/35,!0/50

Depending on the specifications you use, the duration of each MF should be 50ms or 55ms, as it is in the case above. KP is 100ms and ST is 35ms, per Bell System specifications.

Here, we have used 50ms for each number. So it is! If you'd prefer to use 55ms tones, change the first 50 you see for MF1 through M0 to 55 (but not the second one!)

Now, add the following subroutines to your dialplan:

[signallookup] ; NPSTN 20210208 NA 
exten => MF,1,Return("|","!0/${ARG3}","!700+900/${ARG1}|!0/${ARG2}","!700+1100/${ARG1}|!0/${ARG2}","!900+1100/${ARG1}|!0/${ARG2}","!700+1300/${ARG1}|!0/${ARG2}","!900+1300/${ARG1}|!0/${ARG2}","!1100+1300/${ARG1}|!0/${ARG2}","!700+1500/${ARG1}|!0/${ARG2}","!900+1500/${ARG1}|!0/${ARG2}","!1100+1500/${ARG1}|!0/${ARG2}","!1300+1500/${ARG1}|!0/${ARG2}","!1100+1700/${ARG5}|!0/${ARG2}","!1500+1700/${ARG4}|!0/${ARG2}","!900+1700/${ARG4}|!0/${ARG2}","!1300+1700/${ARG4}|!0/${ARG2}","!700+1700/${ARG4}|!0/${ARG2}")
exten => SF,1,Return("|",200,100,"!0/200","!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}") ; ARG1 = frequency (2600), ARG2 = on (60ms), ARG3 = off (40ms) / SFX = interdigit silence

[mfer] ; NPSTN 20181212 NA/BJC, revised 20210208
exten => start,1,Set(number=${FILTER(0-9,${ARG1})}) ; ARG1 = digits to MF
	same => n,Gosub(signallookup,MF,1(50,50,500,35,100)) ; ARG1 = on, ARG2=off, ARG3=silence, ARG4=ST duration, ARG5=KP duration
	same => n,Set(ARRAY(digitsep,mfx,mf1,mf2,mf3,mf4,mf5,mf6,mf7,mf8,mf9,mf0,mfkp,mfst,mfstp,mfst2p,mfst3p)=${GOSUB_RETVAL}) ; R1, CCIT5: STP ~ code 12, ST2P ~ KP2, ST3P ~ code 11
	same => n,Set(c=0)
	same => n,Set(numdigits=${LEN(${ARG1})})
	same => n,Set(tonestring=${mfx}${digitsep}${mfkp}${digitsep})
	same => n,While($[${c}<${numdigits}])
	same => n,Set(mfnext=${mf${number:${c}:1}})
	same => n,Set(tonestring=${tonestring}${mfnext}${digitsep})
	same => n,Set(c=${INC(c)})
	same => n,EndWhile
	same => n,Set(mfnext=${mfst})
	same => n,Set(tonestring=${tonestring}${mfnext}${digitsep})
	same => n,Set(tonestring=${tonestring}${mfx})
	same => n,Set(toneduration=$[100*${LEN(${ARG1})}+1785])
	same => n,PlayTones(${FILTER(0-9\x21/|+,${tonestring})})
	same => n,Wait($[${toneduration}/1000])
	same => n,StopPlayTones()
	same => n,Return()

Below is an expanded MFer than do sequencing MF using different ST tone variations. It is not currently production ready, as longer sequences suffer from more and more of the end being cut off, so an additional timing buffer would need to be refined:

[mfer2] ; NPSTN 20181212 NA/BJC, revised 20210208
exten => start,1,NoOp()
	same => n,Set(lon=50) ; 50
	same => n,Set(loff=50) ; 50
	same => n,Set(lsil=500) ; 500
	same => n,Set(lst=50) ; 35/50
	same => n,Set(lkp=100) ; 100
	same => n,Gosub(signallookup,MF,1(${lon},${loff},${lsil},${lst},${lkp})) ; ARG1 = on, ARG2=off, ARG3=silence, ARG4=ST duration, ARG5=KP duration
	same => n,Set(ARRAY(digitsep,mfx,mf1,mf2,mf3,mf4,mf5,mf6,mf7,mf8,mf9,mf0,mfkp,mfst,mfstp,mfst2p,mfst3p)=${GOSUB_RETVAL}) ; R1, CCIT5: STP ~ code 12, ST2P ~ KP2, ST3P ~ code 11
	same => n,Set(number=${FILTER(0-9,${ARG1})}) ; ARG1 = digits to MF
	same => n,Set(mfstart=${FILTER(0-9,${ARG2})}) ; ARG2 = optional ST delimiter
	same => n,Set(number2=${FILTER(0-9,${ARG3})}) ; ARG3 = optional 2nd group of digits to MF
	same => n,Set(cv=0)
	same => n,Set(numdigits=${LEN(${number})})
	same => n,Set(tonestring=${mfx}${digitsep}${mfkp}${digitsep})
	same => n,While($[${cv}<${numdigits}])
	same => n,Set(mfnext=${mf${number:${cv}:1}})
	same => n,Set(tonestring=${tonestring}${mfnext}${digitsep})
	same => n,Set(cv=${INC(cv)})
	same => n,EndWhile()
	same => n,Set(mfnext=${mfst})
	same => n,ExecIf($["${mfstart}"="mfstp"]?Set(mfnext=${mfstp})) ; R1 STP, CCIT5: Code 12
	same => n,ExecIf($["${mfstart}"="mfst2p"]?Set(mfnext=${mfst2p})) ; R1 ST2P, CCIT5: KP2
	same => n,ExecIf($["${mfstart}"="mfst3p"]?Set(mfnext=${mfst3p})) ; R1 ST3P, CCIT5: Code 11
	same => n,Set(tonestring=${tonestring}${mfnext})
	;same => n,Set(tonestring=${digitsep}${tonestring}${mfx})
	same => n,Set(toneduration=$[$[${lon}+${loff}]*$[${LEN(${number})}+${LEN(${number2})}]+${lsil}+${lkp}+${loff}+${lst}+${loff}])
	same => n,GotoIf($[${LEN(${number2})}=0]?play)
	same => n,Set(cv=0)
	same => n,Set(numdigits=${LEN(${number2})})
	same => n,Set(tonestring=${tonestring}${digitsep}${mfkp}${digitsep})
	same => n,While($[${cv}<${numdigits}])
	same => n,Set(mfnext=${mf${number2:${cv}:1}})
	same => n,Set(tonestring=${tonestring}${mfnext}${digitsep})
	same => n,Set(cv=${INC(cv)})
	same => n,EndWhile()
	same => n,Set(tonestring=${tonestring}${mfst})
	same => n,Set(toneduration=$[${toneduration}+${lkp}+${loff}+${lst}+${loff}]) ; add on aditional KP/ST
	same => n(play),Set(td=0)
	same => n,Set(UNSHIFT(tones,|)=${tonestring})
	same => n,While($["${SET(t=${SHIFT(tones,|)})}" != ""])
	same => n,Set(t=${FILTER(0-9,${CUT(t,/,2)})})
	same => n,Set(td=$[${td}+${t}])
	same => n,EndWhile()
	same => n,Set(toneduration=${td}) ; increased buffer needed for longer strings
	same => n,PlayTones(${FILTER(0-9\x21/|+,${tonestring})})
	same => n,Wait($[${toneduration}/1000])
	same => n,StopPlayTones()
	same => n,Return()

A note about the [mfer] subroutine is perhaps warranted here. The original approach to a dynamic MFer was using a subroutine to loop through each digit and generate an MF tone using the tone definitions in indications.conf. However, this approach, originally tested by Don Froula, does not work for one reason or another. Hence, the idea came about of not using indications.conf but instead dynamically building up a tone string and passing that into PlayTones() all at once. Don Froula, intrigued by the idea, admitted this could work, and Naveen and Brian immediately set upon implementing it. With Brian's help, the complex subroutine you see above now allows a number of any length to be MFed. The KP and ST tones are automatically prepended and appended to the tone string automatically; there is no need to specify this.

To call the MFer, all you need to do is pass in the digits to MF (not including KP and ST) as ARG1, like so: GoSub(mfer,start,1(5551212)).

The above will result in KP + 5 + 5 + 5 + 1 + 2 + 1 + 2 + ST being MFed.

You can now use this MFer on incoming and/or outgoing calls from your node. Perhaps you want to have MF inpulsing on calls interoffice calls to your second exchange:

exten => _${OC1}XXXX,1,GoSub(revertive,start,1(${EXTEN:-4},0,5xb))
	same => n,GoTo(KLondike5,${EXTEN:-4:4},1)
exten => _${OC2}XXXX,1,GoSub(mfer,start,1(${EXTEN}))
	same => n,GoTo(KLondike6,${EXTEN:-4:4},1)

Or perhaps, you only have one exchange code but be really ambitious:

exten => _${OC1}XXXX,1,GoSub(mfer,start,1(${EXTEN}))
	same => n,GoSub(revertive,start,1(${EXTEN:-4},0,5xb))
	same => n,GoTo(KLondike5,${EXTEN:-4:4},1)

Now, external callers would hear MFing, followed by #5XB revertive pulsing.

You have the flexibility to choose from various inpulsing and outpulsing options for your node, but remember to stay "period-correct". You might hear MFing followed by revertive pulsing, for instance, but you would not be likely to hear revertive pulsing followed by MFing! Pay attention to the order of your inpulsing and outpulsing and make sure it stays realistic.

Note: Do not add any outpulsing directly in the dialnpstn subroutine! Instead, create a separate outpulsing context that you call before dialnpstn (or call the particular outpulsing subroutine). Either way, call any outpulsing separately before calling the dialnpstn subroutine.


If you do not already have the [signallookup] subroutine (the same one used for the MFer), you will need that:

[signallookup] ; NPSTN 20210208 NA ; Bell System specs say 1st 50 should be 55? / MFX - silence / ST - Don has 35 here set to 50
exten => MF,1,Return("|","!0/${ARG3}","!700+900/${ARG1}|!0/${ARG2}","!700+1100/${ARG1}|!0/${ARG2}","!900+1100/${ARG1}|!0/${ARG2}","!700+1300/${ARG1}|!0/${ARG2}","!900+1300/${ARG1}|!0/${ARG2}","!1100+1300/${ARG1}|!0/${ARG2}","!700+1500/${ARG1}|!0/${ARG2}","!900+1500/${ARG1}|!0/${ARG2}","!1100+1500/${ARG1}|!0/${ARG2}","!1300+1500/${ARG1}|!0/${ARG2}","!1100+1700/100|!0/${ARG2}","!1500+1700/${ARG4}|!0/${ARG2}","!1300+1700/100|!0/${ARG2}","!900+1700/${ARG4}|!0/${ARG2}","!700+1700/${ARG4}|!0/${ARG2}")
exten => SF,1,Return("|",200,100,"!0/200","!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}","!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}|!${ARG1}/${ARG2}|!0/${ARG3}") ; ARG1 = frequency (2600), ARG2 = on (60ms), ARG3 = off (40ms) / SFX = interdigit silence

Regardless, you will need the [sfer] subroutine:

[sfer] ; NPSTN 20190218 NA, revised 20210208 ; ARG1 = digits to SF
exten => start,1,Set(number=${FILTER(0-9,${ARG1})}) ; ARG1 = digits to do something with
	same => n,Gosub(signallookup,SF,1(2600,60,40))
	same => n,Set(ARRAY(digitsep,sfinter,sfpulse,sfx,sf1,sf2,sf3,sf4,sf5,sf6,sf7,sf8,sf9,sf0)=${GOSUB_RETVAL})
	same => n,Set(c=0)
	same => n,Set(p=0)
	same => n,Set(numdigits=${LEN(${ARG1})})
	same => n,Set(tonestring=)
	same => n,While($[${c}<${numdigits}])
	same => n,Set(nextp=${number:${c}:1})
	same => n,ExecIf($["${nextp}"="0"]?Set(nextp=10))
	same => n,Set(p=$[${p}+${nextp}])
	same => n,Set(sfnext=${sf${number:${c}:1}})
	same => n,Set(tonestring=${tonestring}${sfnext}${digitsep})
	same => n,Set(tonestring=${tonestring}${sfx}${digitsep})
	same => n,Set(c=${INC(c)})
	same => n,EndWhile
	same => n,Set(toneduration=$[${sfpulse}*${p}+${sfinter}*${numdigits}])
	same => n,PlayTones(${FILTER(0-9\x21/|+,${tonestring})})
	same => n,Wait($[${toneduration}/1000])
	same => n,StopPlayTones()
	same => n,Return()

Dial Pulser

Below is the dialplan coded needed for the dial pulser:

[dialpulser] ; NPSTN 20190212 NA
exten => start,1,Set(number=${ARG1}) ; ARG1 = digits to dial pulse
	same => n,Set(c=0)
	same => n,Set(numdigits=${LEN(${ARG1})})
	same => n,While($[${c}<${numdigits}])
	same => n,Playback(custom/dialpulses/dp${number:${c}:1},noanswer)
	same => n,Set(c=${INC(c)})
	same => n,EndWhile
	same => n,Return

ARG1 is simply what number to dial pulse. The number can be of any length (beside 0, obviously).

You will need audio recordings of each number being dial pulsed. You can easily extract dial pulses from various Evan Doorbell recordings. Prefix each digit with dp when you save the audio file, convert to ulaw, and then move to /var/lib/asterisk/sounds/en/custom/dialpulses/.

If you'd prefer to use our dial pulsing audio files, you may download them here.

Conference Bridges

Creating a conference bridge in Asterisk is relatively easy. Here, we will provide a template for creating a kind of so-called "party line" conference bridge, much like those on which Evan Doorbell spent hours talking and listening in the 1970s.

You will need to add the following to confbridge.conf:

sound_join=/var/lib/asterisk/sounds/en/custom/conf/confjoin  ; The sound played to everyone when someone enters the conference.
sound_leave=/var/lib/asterisk/sounds/en/custom/conf/confdis ; The sound played to everyone when someone leaves the conference.




You will need the "confjoin" and "confdis" audio files. You can use whatever audio files you like here; we recommend extracting actual "party line" conference joining and disconnect noises from an Evan Doorbell recording.

To have an extension go to a conference bridge, specify the following in your dialplan: ConfBridge(1,bridge,caller).

In this case, we have given the bridge an ID of 1. This number should be unique for each conference. You may choose to set this equal to the extension on which this conference is located.

You can have multiple extensions go to the same bridge but with different "experiences". For example, you could create a separate extension that joins the same bridge but also features a menu: ConfBridge(1,bridge,caller,volmenu). As you've probably figured out by now, ARG1 for ConfBridge is which bridge to use, ARG2 is which bridge profile to user, ARG3 is which user profile to use, and ARG4, which is optional, is which menu to use, if any. If you're trying to recreate a 1970s "party line", though, omit the menu.

It is important to note that conferences are not created or defined in confbridge.conf! Creating a second conference is as simple as making another extension with a ConfBridge call that uses something different for ARG1. For instance, ConfBridge(2111,bridge,caller) and ConfBridge(052,bridge,caller) would be two different conferences entirely, though they both use the same bridge and user profiles, so the experiences (i.e. sounds and functionality) will be the same.

Non-Supervising Conference Bridges

By default, the ConfBridge() application will supervise with no alternative, unlike the Playback() application which accepts a noanswer argument. Because ConfBridge is often the only way to get much functionality in Asterisk to work as desired, this is a significant limitation; however, modifying the source code can remove this behavior for better operation.

Patch Instructions:

  1. Navigate to the location where Asterisk was compiled, e.g. /usr/src/asterisk-18.whatever — go to the apps directory.
  2. Open app_confbridge.c for editing
  3. Perform a find and replace operation, replacing ast_answer(chan); with //ast_answer(chan);.
    Alternately, from the main Asterisk source directory (e.g. /usr/src/asterisk-18.whatever), run the following sed one-liner: sed -i 's/ast_answer(chan)/\/\/ast_answer(chan)/g' apps/app_confbridge.c
  4. Navigate to the root source folder, e.g. /usr/src/asterisk-18.whatever
  5. Type make and then make install to recompile Asterisk.
  6. Type service asterisk stop and then service asterisk start to restart Asterisk.

ConfBridge() will no longer automatically supervise a call. Once you've patched your Asterisk system, you must manually perform supervisory functions, as follows:

  • This will replicate the previous behavior, by manually answering and supervising the call.
    exten => s,1,Answer()
    	same => n,ConfBridge(mybridge)
    	same => n,Hangup()
  • This will allow two-way audio without answering. Progress allows passing of audio without supervising. The main use case here is for backend mixing of audio channels, such as for intercept, signaling, or operator services.
    exten => s,1,Progress()
    	same => n,ConfBridge(mybridge)
    	same => n,Hangup()
  • This will not perform any supervisory functions at all! The caller may/will not hear anything! For most cases, you should not use this at all:
    exten => s,1,ConfBridge(mybridge)
    	same => n,Hangup()

As of June 2021, Asterisk's app_confbridge now has the answer_channel user option. Set answer_channel = no, with Asterisk 18.5+/Asterisk 16.19+. No patching is required.

You must either use Progress() or Answer(). Failing to do so may result in no audio being passed at all.

Take care to revisit all uses of ConfBridge in your dialplan code to ensure you're using Progress or Answer before any calls to ConfBridge.

Answering Machines

Answering machines and voicemail are, in many ways, the antithesis of each other. Functionally, an answering machine has no place in Asterisk. However, it may be neat to simulate a physical telephone answering device (or answering machine) in Asterisk, to make it sound like somebody has reached an answering machine. This can be done with fairly trivial dialplan code.

Behind the scenes, the VoiceMail application can still be used. For the most part, the caller would never notice the difference. However, by default, Asterisk plays its own beep tone to the caller and this cannot be overridden. The following patch to Asterisk (not yet merged into Digium Asterisk as of April 2021) addresses this issue:

cd /tmp
cd /usr/src/asterisk-18.3.0/
patch -u -b apps/app_voicemail.c -i /tmp/ASTERISK-29349.patch # adds the t option to VoiceMail()
make install
asterisk -rx "core restart now"

This is a relatively simply patch which simply gives the dialplan programmer full control over what beep should be played, or whether one should be played at all. Simply add the t option, specifying a custom beep tone to use, or leaving this argument empty to suppress the beep entirely.

This allows you to use your own outgoing answering machines without a modern Asterisk tone coming on at the end, and you can even use beep tones that are noticably analog and old sounding.

Note that if a user presses the # key while recording, Asterisk will say "thank you" and exit the VoiceMail application (playing auth-thankyou). This patch does not address that behavior. If users are allowed to review messages in voicemail.conf, an additional menu plays afterwards anyways, so it's not merely a question of suppressing an auth-thankyou somewhere, and it's probably best to just leave this behavior be.

Hook Flashing

As of April 2021, Asterisk itself has very minimal support for hook flashing. All Asterisk has historically done is propogate these events across channels from one to the other, if they are compatible. It has not had any ability to do anything with these events or expose them in any way (i.e. AMI).

There are two primary reasons for this:

  • In Asterisk, the "modern" and "hip" way to do certain tasks usually accessed through a hook flash is through special feature codes like *2 that are defined in features.conf
  • ATAs can handle hook flashes locally and perform a small but generally sufficient set of actions, such as Call Waiting, Three Way Calling, etc.

For building advanced functionality, it is essential to be able to handle hook flashes in Asterisk rather than let them be handled locally. In fact, ATAs have better support for this than they do handling these events locally. Grandstream ATAs, for example, will not connect to the switch at all if you flash to spawn a three-way call, even if you have off-hook auto-dial configured. Generally speaking, you are limited to the idiosyncrasies of different ATAs and many events are simply impossible to handle in the desired way. Sending flash events to Asterisk and performing bridge operations switch-side is vastly superior, though it requires a significant investment in configuring such a system (not for the light-hearted!) You will need to handle flash events as desired and implement all relevant functionality yourself, including writing your own Call Waiting from scratch. However, if you are designing an actual Class 5 switch, this is probably what you are trying to do anyways.

Regardless of the purpose, being able to handle flash events increases flexibility. The good news is that as of April 2021, patches to make hook flash events a first class citizen in Asterisk are now available! To add hook flash events to Asterisk, it's as easy as running the following:

cd /tmp
cd /usr/src/asterisk-18.3.0/
patch -u -b channels/chan_sip.c -i /tmp/sip.patch # add application/hook-flash support for SIP INFO flash events
patch -u -b main/file.c -i /tmp/file.patch # ignore flash events and don't throw an error
patch -u -b res/res_rtp_asterisk.c -i /tmp/ASTERISK-29373.diff # don't create duplicate flash events for RTP flashes
patch -u -b configs/samples/stasis.conf.sample /tmp/stasis_sample.patch # AMI flash event
patch -u -b include/asterisk/stasis_channels.h /tmp/stasis_channels.h.patch # AMI flash event
patch -u -b main/stasis_channels.c /tmp/stasis_channels.c.patch # AMI flash event
patch -u -b main/manager_channels.c /tmp/manager_channels.patch # AMI flash event
patch -u -b main/stasis.c /tmp/stasis.patch # AMI flash event
patch -u -b main/channel.c /tmp/channel.patch # stop throwing warning for flash event and handle flash events by triggering AMI event
make install
asterisk -rx "core restart now"

This will expose hook flashes as AMI events. You will need to handle them using an out of band AMI process, such as PAMI for PHP. You can then spawn dialplan execution to perform flash logic and do other operations as desired based on the state of the call(s).

As of April 2021, the patches to add AMI support have been submitted to Digium but are not yet part of the master Asterisk source code. Until the feature is available natively, you simply need to patch your copy and recompile. It should not take more than a minute or two to do.

Credits & Contributions: J. Colp, Digium for correcting improper RTP stream handling of flash events; N.A. for two issues which add application/hook-flash support to SIP, suppress errors in file.c, and adding full AMI (Asterisk Manager Interface) support for flash events.

Announcement Drums

It is relatively simple to set up shared (and even talkable) busy signals or simulated drum announcement machines with Asterisk. The below will cover a simple example of a drum announcement. There are multiple ways to do this and this is just an example.

You will likely want the non-supervising ConfBridge patch for this kind of stuff.

Typically, the idea with these is:

  • If the announcement is idle and it gets a call, "start" the drum up
  • All callers who reach the announcement before it gets to the beginning are all simultaneously cut into it at the same time
  • When no callers are left, the "machine" turns itself off. In software, this is even more important, because there's no legitimate reason to waste resources when nothing needs them

The example below cuts callers into an announcement for one announcement cycle and then sends them to another destination.

The example below supervises when callers are cut into the bridge. For intercepts, this may not be desirable. Instead of the initial Dial(), you could use MusicOnHold() directly and then StopMusicOnHold() instead of Answer() in the [drum]. With the patch to ConfBridge, callers will then be cut into a bridge, and the call still will not have supervised. All depends on how you want to set things up.

The example below is similar to the code that powers Centralized Intercept on NPSTN. POPCORN (767-2676) on NPSTN also uses similar "drum technology". Callers are cut in at the same time and stay on for 4 full cycles of the time from when they entered. Then, they're dropped to a "post-time conference" for about 10 seconds, then disconnected. This is an example of a more elaborate system.

exten => _X!,1,Dial(Local/${EXTEN}@drum,,m(ringback)g) ; drum announcement
	same => n,Goto(somewhere-else,s,1)

exten => _X!,1,Set(GROUP(drumintercept)=1)
	same => n,Set(CDR_PROP(disable)=1)
	same => n,Set(CONFBRIDGE(user,wait_marked)=yes)
	same => n,Set(CONFBRIDGE(user,end_marked)=yes)
	same => n,Set(CONFBRIDGE(user,dtmf_passthrough)=no)
	same => n,Set(CONFBRIDGE(user,startmuted)=yes)
	same => n,Set(CONFBRIDGE(user,dsp_drop_silence)=yes)
	same => n,GotoIf($[${GROUP_COUNT(1@drumintercept)}=1]?startannouncement:waitloop)
	same => n(startannouncement),Originate(Local/1@drum-announcement,exten,announcement-access,1,,a)
	same => n,Wait(0.1)
	same => n(waitloop),GotoIf($["${CONFBRIDGE_INFO(parties,drumintercept1)}"="0"]?startannouncement) ; if nobody is in the conference, start it up
	same => n,WaitForCondition(#,#[#{GROUP_COUNT(1@interceptcutin)}>0])
	same => n(bridge),Answer()
	same => n,ConfBridge(drumintercept1,silentbridge,incogtalkeroptimized)
	same => n,Set(GROUP(drumintercept)=)
	same => n,Hangup()

exten => 1,1,Set(CONFBRIDGE(user,marked)=yes)
	same => n,Set(CDR_PROP(disable)=1)
	same => n,ConfBridge(drumintercept1,silentbridge,incogtalkeroptimized)
	same => n,Hangup()

exten => 1,1,Answer()
	same => n,Set(CDR_PROP(disable)=1)
	same => n,ConfBridge(drumintercept1,silentbridge,incoglistener)
	same => n,Hangup()

exten => 1,1,Answer()
	same => n,Set(CDR_PROP(disable)=1)
	same => n,Wait(${RAND(2,3)})
	same => n,Set(GROUP(interceptcutin)=1)
	same => n,Wait(0.85)
	same => n,Set(GROUP(interceptcutin)=)
	same => n,Set(VOLUME(TX)=2)
	same => n,Playback(custom/intercept-announcement,noanswer)
	same => n,Wait(3)
	same => n,System(asterisk -rx "confbridge kick drumintercept1 all") ; you could kicked all the unmarked users here, instead of all the users
	same => n,Hangup() ; hang up, and kick out everyone presently in the bridge


One would think that ChanSpy() could be used for this kind of thing, but ChanSpy is, for these purposes, almost completely useless, due to a long persistent limitation with audiohooks in Asterisk.

You will need to use ConfBridge. The non-supervising ConfBridge patches are a must-have pre-req. Once you have that, the mechanism is as follows:

  • "Regenerate" the call. We use "regenerate" as pseudo-jargon to precisely mean: generating, via a call file (not via Originate(), due to the significant limitations of this application), a near-identical copy of the current call. This means all channel variables, and of course, the Caller ID, name, and presentation, as well as anything else you want to carry forward henceforth.
  • Drop the original call into a ConfBridge(). Not supervised, so there won't be any answer supervision sent to the caller. The regenerated call is what's actually going to go to the destination, and this will enter the bridge as well.
  • Use the G option for Dial() to fork the call on answer supervision. Once the called party answers, split the caller and callee paths. We don't need the caller path anymore, so get rid of it (Hangup), though be careful with the h extension! Next, set a flag to indicate the call has been answered (e.g. by using a global variable or a DB entry), then drop the original call from the ConfBridge. In the dialplan, it will check whether the call has been answered using that aforeset flag, and if it has, it will Answer() the call and then re-enter the bridge. (Basically, we kick the caller out of the bridge to provide him with answer supervision, then drop him back in.)
  • All in the meanwhile, since we now have a ConfBridge, you can mix whatever audio you want into the bridge. For crosstalk, there's a fairly logical way to do this. You could have unique crosstalk per call or shared crosstalk across all calls, and source this crosstalk from audio files, other conference bridges, etc.

There is more involved, of course. Each call effectively has a unique conference bridge. Hangup handling using the h extension is an art that must be carefully mastered. Because the calling party is no longer directly connected to the called party, you will need to manually check for different cases and kick the other party if one has left. And if you forget to do this in all the required places, then you may find that you have "ghost" bridges with nobody in them piling up as calls take place. When a call is completed, the conference bridge should get destroyed.

Because there are some people interested in this concept, a subset of the crosstalk/carrier code used on NPSTNNA01 is provided below for reference.

;;; Usage example:
exten => _X!,1,Gosub(npstnanifail,check,1) ; ANI FAIL CHECK
	same => n,ExecIf($["${GOSUB_RETVAL}"="1"]?Hangup(29)) ; ANI FAIL CHECK
	same => n,Gosub(carriersetup,out,1(${EXTEN}))
	same => n,GosubIf($[${LEN(${GOSUB_RETVAL})}>0]?regeneratecall,s,1(${GOSUB_RETVAL},carrierbridgeaccess,carrieronward,out,${EXTEN}))
	same => n,GotoIf($[${LEN(${GOSUB_RETVAL})}>0]?carrierbridge,${GOSUB_RETVAL},1)
	same => n(dial),Gosub(npstn-out-verify,${EXTEN},1)
	same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?linerestricted,${EXTEN},1)
	same => n,GotoIf($["${GOSUB_RETVAL}"="0"]?npstn-out-blocked,s,1)
	same => n,Gosub(dialnpstn,start,1(${EXTEN},disable,${zipcode},${maindisa}))
	same => n,Hangup()

;;; Carrier Code:

exten => in,1,Set(CDR_PROP(disable)=1)
	same => n,Gosub(carrierlookup,${ARG1},1)
	same => n,ExecIf($[${GOSUB_RETVAL}=0]?Return)
	same => n,Set(LOCAL(channel)=${FILTER(0-9,${UNIQUEID})})
	same => n,Set(__callednumber=${ARG1}) ; do we really need this?
	same => n,ExecIf($[${GROUP_COUNT(1@carriercalls)}=0]?DBdeltree(carrierchannels))
	same => n,Set(GROUP(carriercalls)=1)
	same => n,Set(GROUP(carriercrosstalk)=1)
	same => n,Set(DB(carrierchannels/${channel})=${CHANNEL})
	same => n,ExecIf($[${GROUP_COUNT(0@carriercrosstalk)}=0]?Originate(Local/s@carriercrosstalk,app,Wait,9999999,,,av(CDR_PROP(disable)=1^CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)})))
	same => n,Set(randl=BCDE) ; Subtle L-Carrier track options (we'll randomly pick one)
	same => n,Set(LOCAL(carriergroup)=${randl:$[${RAND(1,${LEN(${randl})})}-1]:1})
	same => n,Set(SHARED(carriergroup)=${carriergroup})
	same => n,Originate(Local/${carriergroup}@carriernoise,exten,carrierinject,${channel},1,,av(CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)})) ; Main L-Carrier
	same => n,Originate(Local/monitor@carriercrosstalk,exten,carrierinject,${channel},1,,av(CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)})) ; Crosstalk
	same => n,Return(${channel})
exten => out,1,Set(CDR_PROP(disable)=1)
	same => n,Gosub(carrierlookup,${ARG1},1)
	same => n,ExecIf($[${GOSUB_RETVAL}=0]?Return)
	same => n,Gosub(vsc-npstn-numcheck,${ARG1},1)
	same => n,ExecIf($[${GOSUB_RETVAL}=0]?Return)
	same => n,Set(LOCAL(channel)=${FILTER(0-9,${UNIQUEID})})
	same => n,Set(__callednumber=${ARG1}) ; do we really need this?
	same => n,ExecIf($[${GROUP_COUNT(1@carriercalls)}=0]?DBdeltree(carrierchannels))
	same => n,Set(GROUP(carriercalls)=1)
	same => n,Set(GROUP(carriercrosstalk)=1)
	same => n,Set(DB(carrierchannels/${channel})=${CHANNEL})
	same => n,ExecIf($[${GROUP_COUNT(0@carriercrosstalk)}=0]?Originate(Local/s@carriercrosstalk,app,Wait,9999999,,,av(CDR_PROP(disable)=1^CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)})))
	same => n,Set(randl=BCDE) ; Subtle L-Carrier track options (we'll randomly pick one)
	same => n,Set(LOCAL(carriergroup)=${randl:$[${RAND(1,${LEN(${randl})})}-1]:1})
	same => n,Set(SHARED(carriergroup)=${carriergroup})
	same => n,Originate(Local/${channel}@carrierinject,exten,carriernoise,${carriergroup},1,,av(CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)})) ; Main L-Carrier
	same => n,Originate(Local/monitor@carriercrosstalk,exten,carrierinject,${channel},1,,av(CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)})) ; Crosstalk
	same => n,Return(${channel})

[regeneratecall] ; ARG1 = local channel ID, ARG2 = local channel context, ARG3 = context, ARG4 = in/out, ARG5 = called #
exten => s,1,Originate(Local/${ARG1}@${ARG2},exten,${ARG3},${ARG5},1,,av(CALLERID(num)=${CALLERID(num)}^CALLERID(name)=${CALLERID(name)}^CALLERID(pres)=${CALLERID(pres)}^calldirection=${ARG4}^__channel=${ARG1}^__autovonchan=${autovonchan}^__clidverif=${clidverif}^__autovonprioritydigit=${autovonprioritydigit}^__forcesecurechannel=${forcesecurechannel}^__dtlocation=${dtlocation}^__isup-oli=${isup-oli}))
	same => n,Return(${ARG1})

exten => _X!,1,Set(channel=${EXTEN}) ; if caller hangs up, end call
	same => n,Set(CDR_PROP(disable)=1)
	same => n,Progress()
	same => n,Set(carriergroup=${SHARED(carriergroup,${DB(carrierchannels/${channel})})})
	same => n,ExecIf($["${carriergroup}"!="B"]?Set(VOLUME(TX)=-${RAND(7,9)}))
	same => n,ExecIf($["${carriergroup}"!="B"]?Playback(custom/switch/connect/carrierconnect1,noanswer))
	same => n,ExecIf($["${carriergroup}"!="B"]?Set(VOLUME(TX)=1))
	same => n,ConfBridge(channel${channel},silentbridge,incogtalker) ; this MUST NOT ANSWER the call
	same => n,ExecIf($["${SHARED(carrieranswered,${DB(carrierchannels/${channel})})}"="1"]?Answer():Hangup) ; prevent answering the call if it wasn't... actually answered!
	same => n,Set(SHARED(carrieranswered,${DB(carrierchannels/${channel})})=2) ; this is the ${channel} channel so we don't need to explicitly specify it with SHARED
	same => n,ConfBridge(channel${channel},silentbridge,incogtalker)
	same => n,Hangup() ; used was kicked (other party hung up)
exten => h,1,ExecIf($[${CONFBRIDGE_INFO(parties,channel${channel})}>0]?ConfKick(channel${channel},all))

exten => _X!,1,Set(CDR_PROP(disable)=1)
	same => n,Answer() ; bridge audio to caller immediately
	same => n,Wait(${RAND(1,2)})
	same => n,Set(carriergroup=${SHARED(carriergroup,${DB(carrierchannels/${channel})})})
	same => n,Wait(0.5)
	same => n,ExecIf($["${calldirection}"="out"]?Wait(${RAND(1,${RAND(2,5)})})) ; give time for the L-carrier "B-loop" sound to play
	same => n,ExecIf($["${calldirection}"="in"]?Dial(Local/${EXTEN}@intraoffice-connect,,G(split))) ; channel variables will carry
	same => n,ExecIf($["${calldirection}"="out"]?Dial(Local/${EXTEN}@dtnpstn-out,,G(split))) ; channel variables will carry
	same => n,ConfKick(channel${channel},all) ; if called party hangs up without answering, end call
	same => n(caller),Goto(carrieronward-hangup,s,1) ; so h extension doesn't execute
	same => n(split),Goto(caller) ; disconnect caller to get rid of an unnecessary channel
	same => n(callee),Set(SHARED(carrieranswered,${DB(carrierchannels/${channel})})=1)
	same => n,ConfKick(channel${channel},${DB(carrierchannels/${channel})}) ; kick the caller from the bridge, which will Answer() the call and then drop him back in
	same => n,Set(CDR_PROP(disable)=1)
	same => n,Goto(carrierbridgecallee,${channel},1)
exten => h,1,ExecIf($[${CONFBRIDGE_INFO(parties,channel${channel})}>0]?ConfKick(channel${channel},all)) ; if calling party hangs up, end call

exten => s,1,Hangup()

exten => _X!,1,Set(channel=${EXTEN})
	same => n,Set(CDR_PROP(disable)=1)
	same => n,Originate(Local/answer@carriernoise,exten,carrierinject,${channel},1,,a)
	same => n(wait),WaitForCondition(#,#["#{SHARED(carrieranswered,#{DB(carrierchannels/#{channel})})}"="2"],1) ; prevents race conditions, where if the ConfKick before carrieronward,caller has NOT YET taken effect, we don't kill the call (that call must finish before this executes)
	same => n(bridge),ExecIf($[${CONFBRIDGE_INFO(parties,channel${channel})}>0]?ConfBridge(channel${channel},silentbridge,incogtalker))
	same => n,Hangup()
exten => h,1,ExecIf($[${CONFBRIDGE_INFO(parties,channel${channel})}>0]?ConfKick(channel${channel},all)) ; if called party hangs up, end call

exten => _X!,1,Set(CDR_PROP(disable)=1)
	same => n,Answer()
	same => n,ConfBridge(channel${EXTEN},silentbridge,incogtalker)
	same => n,Hangup()

Echo Test

The standard way to create an echo test in Asterisk would be as follows:

exten => 9931,1,GoTo(echo,s,1)

exten => s,1,Answer() ; Echo test with instructions
	same => n,Playback(demo-echotest)
	same => n,Echo()
	same => n,Playback(demo-echodone)
	same => n,Playback(vm-goodbye)
	same => n,Hangup()

We have created a separate context just for the echo test here, as this is generally good practice. Instead of duplicating code should you wish to add another echo test on a different extension, you can simply copy the GoTo statement and send it to the same context.

Note that the use of the s extension is completely arbitrary and could be anything. In macros, you are required to use the s extension, but subroutines often use the start extension instead (but could use anything, including s, as you may see elsewhere in this documentation).

However, this may not be what you want on your node. If you call extension 9931, by dialing, say, 555-9931, you won't immediately be dumped into the echo test. Most Asterisk systems with echo tests use the code above, which plays an introductory message about the echo test first and a concluding message afterward (if you press # as opposed to simply hanging up). If you simply want your extension to go to an echo test (perhaps more realistic if you are trying to stick with the "vintage" theme, you can replace the above [echo] context with the following:

exten => s,1,Progress() ; Echo test with no instructions
	same => n,Echo()
	same => n,Hangup()

Now, you'll immediately enter the echo test when you call extension 9931, and you won't hear anything if you press "#" before it terminates the call. Here, we use Progress instead of Answer, since an echo test is a test number, so it should not provide answer supervision.

Silent Termination

Creating a silent termination line is incredibly simple:

exten => start,1,Wait(86400)
	same => n,Return

What this subroutine actually does, when called, is wait for 86,400 seconds (or 24 hours), before returning. This way, a caller can't call your silent termination line and stay there forever.

To create a silent termination extension, simply add the following subroutine call to an extension:

exten => 9932,1,GoSub(silentterm,start,1)
	same => n,Hangup

Note that this silent termination line will not "supe" (it won't provide answer supervision). You can add an Answer statement first to change that:

exten => 9932,1,Answer
	same => n,GoSub(silentterm,start,1)
	same => n,Hangup

Live Feeds

Asterisk allows you to use the mpg123 utility to play MP3 streams from the Internet. Although it is not so popular anymore, in the mid to late-20th century, it was quite common to be able to listen to live music feeds and radio stations using certain access numbers, allowing people to listen to their favorite stations and news from wherever they were (provided they were willing to foot the long-distance charges). In Asterisk, there are two approaches you can take to play an MP3 stream.

The first approach is to define the stream in musiconhold.conf. For our example here, we will create a stream for the KZSU radio station which, as of this writing, features, among other things, a 1950s 701B SxS electromechanical switch in its office. We will also create a stream for KPFA, the flagship station of the Pacifica Radio Network:

application = /usr/bin/mpg123 -q -s --mono -r 8000 -f 8192 -b 0

application = /usr/bin/mpg123 -q -s --mono -r 8000 -f 8192 -b 0

Now, you will need to add the appropriate dialplan code to access the Music on Hold streams you defined:

exten => 5078,1,Answer()
	same => n,Set(TIMEOUT(absolute)=3600)
	same => n,MusicOnHold(KZSU)
	same => n,Hangup()
exten => 5732,1,Answer()
	same => n,Set(TIMEOUT(absolute)=3600)
	same => n,MusicOnHold(KPFA)
	same => n,Hangup()

The timeouts you see simply prevent the extension from being tied up forever (even though multiple people can access the stream). After 3600 seconds, or 1 hour, the call will automatically be disconnected. This can help ensure callers don't dial into a stream and let it run for perpetuity.

Now by dialing extension 5078 (or KZSU if dialing by letters) or dialing extension 5732 (or KPFA), you can listen to those respective radio stations.

*Technically, Z is not on the telephone dial, so we have substituted 0 for Z, since the two were equated for ZEnith dialing in many areas.

The downside of this approach is that it doesn't scale well. Every MP3 stream you define in musiconhold.conf is running all the time, 24/7/365. While we don't think this makes much sense (and perhaps constitutes a bug, unintentional or otherwise) in Asterisk, that's how it is. We learned this the hard way when we defined around 100 MP3 streams in musiconhold.conf. As soon we entered moh reload at the Asterisk CLI, the server immediately ground almost to a halt. Needless to say, things were not pleasant. We recommend using this approach only if you have a few streams; if you are looking to have more than 5 or 10 streams, a better approach is to not use musiconhold.conf at all but instead to create the stream on demand directly in the dialplan, like so:

exten => 5078,1,Answer()
	same => n,Set(TIMEOUT(absolute)=3600)
	same => n,MP3Player(
	same => n,Hangup()
exten => 5732,1,Answer()
	same => n,Set(TIMEOUT(absolute)=3600)
	same => n,MP3Player(
	same => n,Hangup()

As you can see, musiconhold.conf is not used at all. The advantage of this is that streams are not running when they are not in use. Resources will only be used to play the stream when there is an active call connected to these extensions.

So, which approach should you use? The downside of the latter approach is that a separate stream instance is created for each caller, even if they are all accessing the same stream. Thus, the approach you implement will depend largely on how and by whom your streams will be used. If you expect a lot of callers will be calling a small number of streams, go with the first method. If you want to create a large number of streams and don't expect much traffic to any single stream in particular, you should consider the second method, since you will not dedicating unnecessary resources to playing the streams when they are not in use.

Finally, worth mentioning is that since the latter approach creates the stream on-demand, rather than accessing an "already-on" stream, there is an additional delay when the stream is called of about 1 second. The delay is not really noticable unless you are listening for it, but you may want to keep this in mind if you need a stream to available the split instant you call it. If this is the case for you, then you will want to define the stream in musiconhold.conf so that it is always playing and immediately accessible. That being said, the discrepancy is only about 1 second, so it is not really worth doing this unless you have a good reason. The former approach is recommended if you only have a few streams and expect high traffic to them, but we personally prefer the latter approach as it allows you to have an unlimited number of streams that, provided they are generally low-traffic, will keep resource utilization to a minimum (i.e. none) when idle.

In other words, if streaming is a big part of what you do, but you don't have too many, define your streams in musiconhold.conf. Otherwise, don't; simply create the streams on demand.

Loop Arounds

Loop arounds are test lines that, while not as common in the PSTN today as they used to be, are still around if you know where to look (or rather, what to call). Of course, you can create your own loop arounds as well!

For those unfamiliar, a loop around is a pair of numbers, generally two consecutive numbers, which could be anything, used by telephone technicians. In the heyday of phreaking, they were prone to abuse by phreaks who would use them to anonymously talk to other people without having to give out their real phone number. If one person called one number and another called the corresponding number of the pair, they would be bridged together — free of charge.

In a loop around pair, while waiting for somebody to call the other line, one of the numbers functions as a milliwatt test while the other functions as a silent termination test. As soon as the other number is called, however, the two lines are immediately bridged. Either the low or high number, in theory, can be the tone or silent side, and it doesn't matter which line is called first or by whom.

Thanks to John Covert for the following code to create loop arounds or "loops" in Asterisk.

[looptest] ; ARG1 is the unique loopid; ARG2 is "tone" for the tone side and nothing for the silent side
exten => s,1,Wait(1)
	same => n,Answer()
	same => n,Set(loopid=${ARG1})
	same => n,Set(looptone=${ARG2})
	same => n,Set(GROUP(side)=looparound_${loopid}${ARG2}) ; only one caller per side of the loop
	same => n,NoOp(${SET(gc=${GROUP_COUNT(looparound_${loopid}${ARG2}@side)})})
	same => n,GotoIf($[${gc}>1]?busy)
	same => n,Set(GROUP(looparound)=looparound_${loopid}) ; This group is for who's first.  Both sides are in it.
	same => n,Goto(loop,s,1)
	same => n(busy),Set(GROUP()=)
	same => n,GoTo(stepbusy,s,1) ; only one user on any side at a time

exten => s,1,NoOp(${SET(gc=${GROUP_COUNT(looparound_${loopid}@looparound)})})
	same => n,GotoIf($[${gc}=2]?bridge) ; First Caller waits, second caller bridges
	same => n,Set(loopdbdel=1) ; only this side deals with creating and deleting the database
	same => n,Set(DB(loop/${loopid})=${CHANNEL})
	same => n,ExecIf($["${looptone}" = "tone"]?Playtones(1004/10000,0/2000))
	same => n,Wait(360000) ; 100 hours max wait time for the other guy to show up.
	same => n,Goto(1)
	same => n(bridge),Set(loopchan=${DB(loop/${loopid})})
	same => n,GotoIf($["${loopchan}" = ""]?1) ; Other side hung up just as we arrived, so we're now the first side
	same => n,Bridge(${loopchan})
	same => n,Goto(1)
exten => h,1,ExecIf($["${loopdbdel}" != ""]?DBdel(loop/${loopid}))

A loop pair then might be created as follows:

exten => 0041,1,GoSub(looptest,s,1(${EXTEN:-4:3},tone))
	same => n,Hangup()
exten => 0042,1,GoSub(looptest,s,1(${EXTEN:-4:3}))
	same => n,Hangup()
exten => 9996,1,GoSub(looptest,s,1(${EXTEN:-4:3},tone))
	same => n,Hangup()
exten => 9997,1,GoSub(looptest,s,1(${EXTEN:-4:3}))
	same => n,Hangup()

If you have multiple exchanges, you might opt to adopt the following approach instead:

exten => _004[1-2],1,GoTo(looparound,${OC1}${EXTEN},1)
exten => _999[6-7],1,GoTo(looparound,${OC1}${EXTEN},1)

exten => _004[1-2],1,GoTo(looparound,${OC2}${EXTEN},1)
exten => _999[6-7],1,GoTo(looparound,${OC2}${EXTEN},1)

exten => _NNX0041,1,GoSub(looptest,s,1(${EXTEN:-7:6},tone))
	same => n,Hangup()
exten => _NNX0042,1,GoSub(looptest,s,1(${EXTEN:-7:6}))
	same => n,Hangup()
exten => _NNX9996,1,GoSub(looptest,s,1(${EXTEN:-7:6},tone))
	same => n,Hangup()
exten => _NNX9997,1,GoSub(looptest,s,1(${EXTEN:-7:6}))
	same => n,Hangup()

Adopt whichever approach makes sense for you. The important thing to note is that if you strip the very last digit of all of your loop numbers, none of them should be identical unless they are part of the same loop pair. What this means is that if you have 0041 and 0042 setup as a loop pair, you can't have 0048 and 0049 also setup as a loop pair, because "004" is used to identify the loop pair and you've now created two supposedly different "pairs" that are, not, in fact, going to work properly. As long as you don't create more than one loop around pair in the same 10s group, you'll be fine.

ChanSpy Verification

The following would allow you to "tap" any of your lines. Optionally, if you would also like to allow the network operator access to a node's local verification trunks for troubleshooting and test purposes (which is recommended in keeping with the theme of the network), please leave us a message at BE1-9942 please make your node eXtensible.

There are several levels of "channel spying" in Asterisk. You can simple listen to the channel, you can whisper to one of the parties (the calling party/owner of the channel), and you can barge into the conversation and be heard by both parties (interrupt). You should use the "listen" option for busy-line verification; the "barge" option should be used for busy-line interrupt. To allow the NPSTN operator to do busy-line verification and interrupt on lines on your node, please leave a message at the number above.

Here is the dialplan code needed to use ChanSpy, assuming the variable chan is at this point the name of the SIP device or channel on which to spy.

Listen only (Verification):

	same => n,ChanSpy(SIP/${chan},q)


	same => n,ChanSpy(SIP/${chan},qw)

Barge (Interrupt):

	same => n,ChanSpy(SIP/${chan},qB)

The 'q' option will not play a beep/announce the channel's name before tapping into the line (in all cases, this is audible by you only, not the spied-on channel). In conjunction with q and other options, you can use v(N) to change the volume where N is between -4 and 4. You can read further about ChanSpy on the VoIP Info site.

T1 Trunks

You can easily setup virtual T1 trunks in Asterisk. Here's how to set up the virtual NICs in Debian 9:

  1. Run the following commands from your terminal:
    sudo apt-get update
    sudo apt-get install build-essential
  2. Enable TUN/TAP on your machine. You may need to modprobe tun to load the tun Linux kernel module. If you are using a hosted VM, you may need to enable "tun" with the virtual machine tun enable switch (it may say "TUN/TAP ON").
  3. Download these 3 attached source code files to your Asterisk server: addr1.c | addr2.c | taptap-modified.c — you can also use wget like so:
  4. Navigate to the directory in which the above 3 files are located. Compile them with the following commands:
    gcc taptap-modified.c -o taptap
    gcc addr1.c -o addr1
    gcc addr2.c -o addr2

    You now have 3 executable binaries: taptap, addr1, and addr2:

    taptap creates two virtual NICs named "tun0" and tun1" with random MAC addresses, and sets up the virtual crossover cable buffers. You can see these along with the hardware NICs with ifconfig -a

    addr1 sets the tun0 random MAC address to 11:11:11:11:11:11

    addr2 sets the tun1 random MAC address to 22:22:22:22:22:22

  5. Now, move the binaries to sbin so they are in an executable path and change the permissions:
    mv /root/addr1 /sbin/
    mv /root/addr2 /sbin/
    mv /root/taptap /sbin/
    sudo chmod 755 /sbin/addr1
    sudo chmod 755 /sbin/addr2
    sudo chmod 755 /sbin/taptap
  6. taptap should be run at an elevated user priority of "-20" for best performance. Use the "nice" command to invoke in the background:
    nice -n -20 taptap&
  7. Then run addr1 and addr2 normally to change the MAC addresses of each interface. You can see the effects of addr1 and addr2 with ifconfig -a
  8. Then, the interfaces must be brought "up" with ifconfig tun0 up and ifconfig tun1 up. Then they are ready for use by ProjectMF. Invoking these needs to be done at VM startup in a script, before Asterisk and Dahdi are started. Of course, all this can be done in a startup script before Zaptel and Asterisk are started.

  9. Now, we need to get Asterisk to use the created interface:

  10. Next step


Contrary to what you may have read elsewhere on the web, you can setup virtual modems in Asterisk! Here, we will set up a virtual modem and use that to create a BBS.

  1. Enter the following in the terminal to install Telnet client and server, as well as other utilities you may need:
    sudo apt-get install telnetd -y
    sudo apt-get install telnet -y
    sudo apt-get install xinetd -y
    sudo apt-get install tcpd -y
    service xinetd restart
    sudo apt-get install lynx -y — text/line mode text browser
    sudo apt-get install less -y — navigation of large outputs
    sudo apt-get install libxml2-utils -y — HTML parser
    sudo apt-get install bc -y — Basic Calculator
    sudo apt-get install sshpass -y — SSH Auto-Password Passer
    sudo apt-get install lynx -y — SSH Auto-Password Passer
  2. If telnet does not work, or later stops working, follow the steps in these two StackOverflow answers:


    Here are the critical steps from the two answers linked above:

    1. Add telnet stream tcp nowait telnetd /usr/sbin/tcpd /usr/sbin/in.telnetd to /etc/inetd.conf.
    2. Make this your default in /etc/xinetd.conf:
      Simple configuration file for xinetd
      # Some defaults, and include /etc/xinetd.d/
      # Please note that you need a log_type line to be able to use log_on_success
      # and log_on_failure. The default is the following :
      # log_type = SYSLOG daemon info
      instances = 60
      log_type = SYSLOG authpriv
      log_on_success = HOST PID
      log_on_failure = HOST
      cps = 25 30
    3. sudo /etc/init.d/xinetd restart
    4. sudo apt-get install tcpd
  3. For this sample, we will be using username "com" and password "com". Wherever you see "com", replace with the info for your system. Enter the following in your terminal:
    nano /etc/
    Delete the text Debian GNU/Linux 9 and replace it with text like:
    Welcome to the BBS! Login with username "com" and password "com"
    Press CTRL+X to save, then Y to confirm, then press ENTER.
  4. Now, create the account "com" with password "com":
    sudo adduser -p $(openssl passwd -l com) com (the first "com" is the password, the second is the password)
  5. To change the password last type sudo passwd com and it will ask for the new password.

    To get the user ID of the user, type id -u com.

    To make this a passwordless account, first modify /etc/ssh/sshd_config and change #PermitEmptyPasswords no to PermitEmptyPasswords yes
    Then, pw usermod com -w none

  6. Now, create a blank shell script that will be used as the entry point for loopback Telnet sessions initiated from Asterisk:
    touch /home/com/
    usermod -s /home/com/ com
    chmod a+x /home/com/
    The second line changes the home directory of "com" to the shell script we created, locking the user's connection into that script. The last line sets the proper permissions. At this point, this shell script doesn't do anything; we'll take care of that once the other mechanics are set up properly.
  7. Alternate Instructions: adduser com; sudo chsh -s /home/com/ com

  8. The following lines remove the normal login information spew provided to users upon initial connection to the server. This does affect all users, but this provides a necessary improvement in realism:
    touch /home/com/.hushlogin
    rm -rf /etc/motd
    touch /etc/motd
  9. Allow certain system access to the controlled shell environment:

    chmod 777 -R /var/spool/asterisk chmod 777 -R /home/com/log
  10. Now the mechanism to initiate a Telnet session from Asterisk has been created. Now, we will install a softmodem in Asterisk that can connect to a shell script via the Telnet mechanisms we just set up:

    mv app_softmodem.c /usr/src/asterisk-13.24.0/apps/
    cd /user/src/asterisk-13.24.0/apps/
    make apps
    make install
    service asterisk restart
  11. Now, modify your dialplan to offer callers a way to connect to your softmodem. Here is the dialplan syntax:
    exten => btx,1,Answer()
    	same => n,Softmodem(host, port, options)
    	same => n,Hangup()
    Without any arguments the application acts as a V.23 modem and connects to a Telnet server (port 23) on localhost.
    Options are:
    	r(...): rx cutoff (dBi, float, default: -35)
    	t(...): tx power (dBi, float, default: -28)
    	v(...): modem version (default: V23):
    			V21        - 300/300 baud
    			V23        - 1200/75 baud
    			Bell103    - 300/300 baud
    			V22        - 1200/1200 baud
    			V22bis     - 2400/2400 baud
    	l or m: least or most significant bit first (default: m)
    	d(...): amount of data bits (5-8, default: 8)
    	s(...): amount of stop bits (1-2, default: 1)
    	u:      Send Ulm Relay Protocol header to Telnet server
    	n:      Send NULL-Byte to modem after carrier detection (Btx specific)
    The modem seems to work fine with VOIP as long as you use a codec like G.711 (alaw/ulaw). Please deactivate any echo cancellation you might use.
    In your dialplan, to use a "Bell 103" 300 baud modem, you might use the following:
    exten => 4221,1,Answer()
    	same => n,Softmodem(,23,v(Bell103)ld(8)s(1)un)
    	same => n,Hangup()
  12. Reload your dialplan by typing asterisk -r and then dialplan reload
  13. At this point, you will be able to make a call to your Asterisk softmodem, which will establish a loopback Telnet connection that will launch the shell script we created earlier. However, at this point it is still blank, so nothing will happen. From here on, you will need to setup everything else you need and launch it from this main shell script. You can use other kinds of scripts, like PHP scripts to read values from databases and perform external authentication using databases, as well. You could, for example, store usernames and passwords to access your system in a database to which you connect using PHP as opposed to keeping track of authentication information locally. Or, the database could be on the same server. Or, you could not use databases at all. From here, everything is called using shell scripts, so you have the freedom to set things up how you like.
    The tutorial for creating a softmodem ends here. You will need shell scripting experience to code up a BBS. You can call the one created in this example at 564-4221 on NPSTN.


Asterisk actually has very good fax support built in (not including the fax stuff that Digium sells), so if you aren't using it, you're missing out.

Receiving faxes in Asterisk is easier than sending them, purely for logistical reasons. It's easy to have an incoming fax emailed to you. It's a bit more involved to receive documents in a compatible format and get them onto the system in a way that they can be sent.

Here are good starting resources for receiving and sending faxes:

To make incoming faxes behave like voicemails, you can call a System command in the hangup handler to have incoming faxes emailed to you as TIF files.

The link above discussing outgoing faxes discusses an approach for doing this. However, it may be more feasible and conveient to design a secure file upload page than to process incoming emails for fax delivery.

MF/ACTS Detectors

ACTS when used here is an abuse of notation. More properly, they are known as single-slot coin denomination tones and were used in ACTS, among other things.

In December 2019, Asterisk source files, centered around dsp.c, were successfully forked to add MF and ACTS detection support to Asterisk.

Credits: Dylan Cruz, Howard Harte, Joshua Stein, Naveen Albert

Detection support is not integrated, but rather a separate program that can be called at will when a detector is needed. In other words, if you need an ACTS detector, you can bring one into the call and then get rid of it, much like how ACTS detectors are used for PSTN (non-COCOT) payphones.

Thanks to MF and ACTS support, it is now possible to have Project MF in modern Asterisk!

ZIP Download:

The ZIP folder contains the following directories:

  • arm
  • x86

The _arm folder contains code for the ARM architecture (e.g. Raspberry Pi). The x86 folders are for x86/i386.

The contents of the appropriate subfolder of the detect folder should be extracted to /etc/asterisk/scripts/detect. You will need to choose the subfolders specific to your architecture. So, if you are running x86, you would extract detect/x86/mf to /etc/asterisk/scripts/detect/mf — don't preserve the x86 or arm in your file hierarchy — choose the architecture you need and place its subfolders right into /etc/asterisk/scripts/detect/

Note that the detector application is named mf for both the MF and ACTS detectors. Only the folder name distinguishes the two; the executable name should not be changed, and there is no extension.


ACTS tones are used for payphone coin signalling on modern non-COCOTs, succeeding the gong-style signalling used in 3-slot payphones. MF and 2600 are part of the multifrequency and singlefrequency signaling systems. DP is audible dial pulsing for real-time dial pulsing, which is generally not possible with VoIP connections.

There are three actual detectors that work separately:

  • /etc/asterisk/scripts/detect/mf/mf — MF and 2600 Hz detector
  • /etc/asterisk/scripts/detect/acts/acts — ACTS detector
  • /etc/asterisk/scripts/detect/dp/dp — Dial Pulse detector

The ACTS detector is the simplest to use. For each detected ACTS beep (each of which is worth 5 cents), it returns one $ (dollar-sign) symbol.

Likewise, the dial pulse detector will return one P for each dial pulse.

The MF/2600 detector is one detector that supports multifrequency, single frequency, and 2600 Hz (general) detection. For multifrequency 0 through 9, it returns 0 through 9. For KP, it returns * and for ST, it returns #. For every single instance of 2600, it returns one "S". Thus, one loud constant 2600 burst (such as a trunk reset) returns a single "S"; dial-pulsing 2600 Hz (i.e. single-frequency signaling) at 10pps or 20pps will produce one S per pulse, making it possible to detect single-frequency dialing in addition to single 2600 Hz tones.

Here is a simple subroutine that uses the ACTS detector (on x86/i386 architecture, but that can be changed), to listen for ACTS tones. Notice that it automatically calculates how many "$"s appeared (\x24 is the hex escape code for $).

[acts-read] ; NPSTNNA 20200104 ; ARG1 = max silence, ARG2 = max duration, RETURNs # of ACTS beeps
exten => s,1,Set(ss=${STRFTIME(${EPOCH},,%s)})
	same => n,Set(file=/tmp/${UNIQUEID}-${ss}.wav)
	same => n,Record(${file},${ARG1},${ARG2},qx) ; q = quiet, x = ignore terminator keys
	same => n,Set(beeps=${SHELL(/etc/asterisk/scripts/detect/acts/acts ${file})})
	same => n,Set(beeps=${FILTER(\x24,${beeps})}) ; \x24 = hex for "$"
	same => n,Set(num=${LEN(${beeps})})
	same => n,NoOp(ACTS: ${beeps} -> ${num})
	same => n,Return(${num})

This detector is used on the NPSTN payphone trunks. So if you're thinking of using this detector to program a payphone controller… it's already been done! Applications of the detectors have already been fairly rigorously exhausted for everyone's benefit, and we've made the applications that make use of these detectors available as well. If you would like to help improve these end-applications or have ideas, please contact the Business Office.

NPSTN Cadence Plan

The NPSTN Cadence Plan allows for the use of standardized custom ring cadences across the network. These are used most commonly for party ling (coded) ringing, busy line callback, and priority ring on calls with greater than routine precedence. The cadences and their numbering were carefully chosen to minimize conflicts with the Bellcore specifications while allowing for maximal functionality and accessibility on NPSTN.

Grandstream ATAs typically allow for 10 cadences, while Linksys ATAs typically allow for 8 (sometimes 9) cadences.

Cadence #Cadence LengthCadence DescriptionGrandstreamLinksys
16.0Standard (US)c=2000/4000;60(2/4)
103.0S-S (Standard UK)c=400/200-400/2000;60(.4/.2,.4/2)

If your ATA uses different formats for specifying cadences, please contact the Business Office and we can work with you to get your ATA set up correctly.

Subroutines like the following may be used to send the right cadence to your ATA:

[SIP-RingHeader] ; ARG1 = cadence / ARG2 = peers
exten => s,1,Set(peerstocheck=${ARG2})
	same => n,Set(i=-1)
	same => n(checkfree),Set(i=${LEN(${CUT(peerstocheck,&,1)})})
	same => n,Set(nextpeer=${peerstocheck:0:${i}})
	;same => n,Set(nextpeer=${nextpeer:4})
	same => n,Set(peerstocheck=${peerstocheck:$[${i}+1]})
	same => n,GotoIf($["${nextpeer:0:4}"="SIP/"]?sip)
	same => n,Goto(next)
	same => n(sip),Set(nextpeer=${CUT(nextpeer,/,2)})
	same => n,Set(agent=${FILTER(A-Za-z0-9\x20\x2E\x2F\x2D,${SHELL(asterisk -rx 'sip show peer ${nextpeer}' | grep "Useragent" | grep -o ':.*')})})
	same => n,Gosub(SIP-RingHeader-indiv,s,1(${ARG1}, ${agent:1})) ; Trim space that was after the : from agent
	same => n(next),GotoIf($[${LEN(${peerstocheck})}=0]?allchecked:checkfree)
	same => n(allchecked),Return()

[SIP-RingHeader-indiv] ; ARG1 = cadence / ARG2 = SIP ATA user agent
exten => s,1,NoOp(${ARG2}) ; if we know what user agent this is, we'll only send that header
	same => n,GotoIf($[${REGEX("Grandstream" ${ARG2})}=1]?grandstream,1)
	same => n,GotoIf($[${REGEX("Linksys" ${ARG2})}=1]?linksys,1)
	same => n,GotoIf($[${REGEX("OBIHAI" ${ARG2})}=1]?obi,1)
	;same => n,SIPAddHeader(Alert-Info: info=) ; this is a bad fallback
	same => n,Goto(grandstream,1) ; fallback to Grandstream
exten => grandstream,1,SIPAddHeader("Alert-Info:\;info=ring${ARG1}") ; Supports 1-10
	same => n,Return()
exten => linksys,1,SIPAddHeader("Alert-Info:\;info=Bellcore-r${ARG1}") ; Linksys (e.g. PAP2T), etc.
	same => n,Return()
exten => obi,1,SIPAddHeader(Alert-Info:${ARG1:-1}) ; send only a single digit
	same => n,Return()

You could then set the cadence by calling it like: same => n,Gosub(SIP-RingHeader,s,1(${EXTEN},${ARG3})) (assuming EXTEN is the ring cadence and ARG3 is the SIP peer).

The SIP header should only get sent once, so if the cadence could be changed before ringing the phone, keep track of that in a separate variable. When determining the cadence to use, consider the priority of the call (MLPP) as well.

Multiple Level Precedence and Preemption

MLPP is most commonly associated with AUTOVON, the old automatic voice network, which was used by the military for much of the Cold War. AUTOVON implemented MLPP. MLPP uses the 4th-column DTMF keys (A, B, C, and D), used as FO (flash override), F (flash), I (immediate), and P (priority). If you don't have 4th-column DTMF, you can use a silver box, such as this one.

Priority Audible Ring replaced normal ring for calls within AUTOVON. It consists of 440 Hz + 480 Hz at -16 dBm0/frequency on for 1.65 seconds and off for .35 seconds.

Preemption Tone is provided to both parties of a connection that a priority call from the AUTOVON network preempts. Preemption Tone is 440 Hz and 620 Hz at -18 dBm0/frequency steady for anywhere from three to fifteen seconds.

MLPP is fairly straightforward to use in Asterisk, and is currently used on NPSTNNA01, which is a multi-function switch (e.g. dial 231-1111 if you are not served by this switch and don't have a foreign exchange line). If you encounter an "all circuits busy" condition, you may be able to complete your call by preempting a lower-priority call.

The following priority levels are usable by the following populations:

  • Flash Override - Switch Owners, only
  • Flash - Local switch users, also
  • Immediate & Priority - Any legitimate and verified NPSTN caller

Note that this means PSTN callers, etc. cannot request priority of any level for any call.

If all circuits remain busy and your call is urgent, dial 0. Operator-assisted calls are completed using a different trunk group.

Further Resources:

The NPSTN MLPP/AUTOVON library is relatively small:

[autovoninit] ; Returns number of current trunk calls
exten => _[A-D0],1,NoOp(AUTOVON Precedence: ${EXTEN}) ; AUTOVON checks
	same => n,Gosub(currentldcalls,s,1)
	same => n,GotoIf($[${GOSUB_RETVAL}=0]?:database)
	same => n,DBdeltree(autovonA) ; Reset the key families, to wipe out old, unneeded data (none of which is currently needed)
	same => n,DBdeltree(autovonB)
	same => n,DBdeltree(autovonC)
	same => n,DBdeltree(autovonD)
	same => n,DBdeltree(autovon0)
	same => n(database),Set(GROUP(autovon)=${EXTEN})
	same => n,Set(DB(autovon${EXTEN}/${FILTER(0-9,${UNIQUEID})})=${CHANNEL})
	same => n,Return()

exten => s,1,GotoIf($["${autovonprioritydigit}"="0"]?done) ; can't preempt any calls
	same => n,Set(callids=${DB_KEYS(autovon0)})
	same => n,GotoIf($[${LEN(${callids})}>0]?0,1)
	same => n(ad),GotoIf($["${autovonprioritydigit}"="D"]?done) ; can't preempt other priority calls
	same => n,Set(callids=${DB_KEYS(autovonD)})
	same => n,GotoIf($[${LEN(${callids})}>0]?D,1)
	same => n(ac),GotoIf($["${autovonprioritydigit}"="C"]?done) ; can't preempt other immediate calls
	same => n,Set(callids=${DB_KEYS(autovonC)})
	same => n,GotoIf($[${LEN(${callids})}>0]?C,1)
	same => n(ab),GotoIf($["${autovonprioritydigit}"="B"]?done) ; can't preempt other flash calls
	same => n,Set(callids=${DB_KEYS(autovonB)}) ; so, yes, only a flash override can get to this point
	same => n,GotoIf($[${LEN(${callids})}>0]?B,1)
	same => n(done),Return() ; Sorry, can't preempt anything
exten => _[A-D0],1,Set(callid=${CUT(callids,\,,1)})
	same => n,Set(preemptchan=${DB(autovon${EXTEN}/${callid})})
	same => n(preemptcall),ChannelRedirect(${preemptchan},autovonpreempted,s,1) ; Set(redirect=${FILTER(A-Za-z0-9\x2E,${SHELL(asterisk -rx 'channel redirect ${preemptchan} autovonpreempted,s,1' | grep "failed")})})
	same => n,Set(deleted=${DB_DELETE(autovon${EXTEN}/${callid})}) ; call isn't up anymore, so delete from DB
	same => n,Goto(a0) ; try again - hunt for another call to preempt
	same => n(success),Return()

exten => _[ABCD].,1,Progress()
	same => n,Playback(custom/switch/autovon/autovon603&custom/switch/autovon/autovon603,noanswer) ; audio file with 603 intercept
	same => n,Hangup()

exten => s,1,Set(GLOBAL(autovonpreempted${FILTER(0-9,${UNIQUEID})})=1)
	same => n,Set(GROUP(autovon)=)
	same => n(preempt),PlayTones(440+620) ; can also be defined as preemption in indications.conf
	same => n,Wait(15) ; Play preemption tone to caller for 15s
	same => n,StopPlayTones()
	same => n,Hangup(8) ; AST_CAUSE_PRE_EMPTED

exten => _X!,1,Wait(0.3) ; wait ever so slightly, to give the variable time to get set
	same => n,ExecIf($["${$[autovonpreempted${EXTEN}]}"="1"]?PlayTones(440+620):Hangup()) ; can also be defined as preemption in indications.conf
	same => n,Wait(3) ; Play preemption tone to caller for 3s
	same => n,Hangup(8) ; AST_CAUSE_PRE_EMPTED

exten => s,1,Return($[${GROUP_COUNT(A@autovon)}+${GROUP_COUNT(B@autovon)}+${GROUP_COUNT(C@autovon)}+${GROUP_COUNT(D@autovon)}+${GROUP_COUNT(0@autovon)}])

Some assumptions and simplifications are necessary for using MLPP on NPSTN. Since trunking is peer to peer using IAX2, scarcity is not really a thing. Thus, artificial limitations must be introduced for preemption to ever occur. However, priority ringing is useful and possible without placing artificial limitations on trunking.

To use the library, do the following:

  • Allow dialing of digits A through D at dial tone. Routing must account for this (e.g. exten => _[A-D].,1,Goto(something,${EXTEN},1))
  • If an A through D was dialed as the first digit, set the channel variable __autovonprioritydigit to the letter. Otherwise, set it to 0.
  • Set a global variable called autovonmaxcalls, equal the maximum number of trunk calls that may be up at any time (not the same as the number of calls shown in the Asterisk console, but rather 1:1 trunk calls)
  • Calls may fail for one of two reasons:
    • The caller does not have sufficient privileges to use a desired priority
    • All circuits are busy

    Both should be properly handled, in a manner such as the following

    same => n,Gosub(currentldcalls,s,1)
    same => n,GotoIf($[${GOSUB_RETVAL}<${autovonmaxcalls}]?proceed)
    same => n,Gosub(autovonpreempt,s,1) ; Attempt to preempt a call
    same => n,Wait(3.2) ; may need to adjust to all group count to fall when preempted call clears
    same => n,Gosub(currentldcalls,s,1)
    same => n,GotoIf($[${GOSUB_RETVAL}<${autovonmaxcalls}]?proceed)
    same => n,GotoIf($["${autovonprioritydigit}"=""]?acb)
    same => n,GotoIf($["${autovonprioritydigit}"="0"]?acb)
    same => n,Progress() ; all circuits busy and no calls preemptable
    same => n,Playback(custom/switch/autovon/autovon601,noanswer) ; for priority callers with insufficient precedence to preempt
    same => n,PlayTones(congestion)
    same => n,Wait(30)
    same => n,StopPlayTones()
    same => n,Hangup()
    same => n(acb),Playback(custom/allcktsbusy,noanswer)
    same => n,Hangup()
    same => n(proceed),Gosub(autovoninit,${autovonprioritydigit},1) ; we can proceed with the call...
  • On a successful call that leaves your switch, call Gosub(autovoninit,${autovonprioritydigit},1) as shown above. This stores information about the call in the database so that we can preempt this call later if needed.

  • To handle preemption, modify your [dialnpstn] to add the F option as follows:
    same => n(dial),Dial(${lookup},,gF(autovonpreempted-callee,${FILTER(0-9,${UNIQUEID})},1))

It may be desirable to prevent certain callers from using certain priorities. As an example, on NPSTNNA01, non-NPSTN callers may not use any priorities. Any NPSTN caller may use the D and C (Priority and Immediate) priorities. Only NPSTNNA01 lines may use the B (Flash) priority. Only the switch owner may use the A (Flash Override) priority. This arrangement is not included above, and would be done when we receive the digits _[A-D] and set the autovonprioritydigit variable. If a caller does not have the necessary privileges, he would be routed as follows: Goto(autovonpreemptfail,${EXTEN},1). The exact classes of service each node desires to use may vary and are not prescribed. It is merely a suggestion that this behavior should be restricted by some class of service accordingly.

Priority Audible Ringing and Priority Ring are enabled by sending a custom ring cadence header to the FXS port before ringing the subscriber line. This can be done by checking the value of ${autovonprioritydigit} in the dialplan. If this value is strictly equal to A, B, C, or D (but not 0 or empty), then priority ring should be used. This corresponds to cadence 8 in the NPSTN Cadence Plan.

Priority Audible Ring is easy: Set(dialoptions=r(priorityring)).

To send the right ring cadence to your ATA, use same => n,Gosub(SIP-RingHeader,s,1(${EXTEN},${ARG3})). See Cadences for this subroutine.

The audio files referenced above are not included and may be recorded locally using the appropriate verbiage.

The latest copy of the verification subroutines ensure MLPP information is transmitted between nodes and will ensure both priority ringing on the called telephone (assuming correct cadence setup).

If the preemption aspect above seems a bit silly, well, it is. In a full MLPP environment, MLPP status would be kept track of per-trunk. The NPSTN MLPP code is a simplified and dumbed down version of full MLPP routines that keep track of priorities for each trunk in each trunk group. Of course, on NPSTN, there is only one "trunk group".

Manual Service

If you would like to be served by manual service instead of dial service, your ATA should off-hook auto-dial 950-0100, which is a free call. This is a manual operator service trunk. All your calls will go through the operator.

If you want it to go directly to Brian specifically, use 950-0101.

Vertical Service Codes (Custom Local Area Signaling Services)

Vertical service codes are dialed by prefacing the code with * from a Touch-Tone phone or 11 from a pulse dial phone (e.g. to use vertical service code 98 one would dial *98 or 1198.)

Note that while many VSCs are NPSTN-specific, they are intended to be dialed from local switch dial tone as either *XX or 11XX. For NPSTN trunk compatability, they may also be dialed from an NPSTN trunk as *XX or 111XX (note the extra 1!). This is to avoid conflicting with 11N numbers on the network). The codes have been chosen to mirror the codes used on the U.S. PSTN by major RBOCs/ILECs as well as some of those additionally used by some VoIP ATAs.

Vertical service codes must be supported by individual end offices. Not all end offices support all (or any) custom calling features. At this time, the list below reflects features fully supported and available on NPSTNNA01T.

Custom Calling Services

Custom calling services are directly integrated into the NPSTN billing system. Usage of custom calling services will be reflected on the bill for the lines on which you use those services. Per Month charges are recurring while Per Use charges apply each time a feature is used.

ACTIVATE/DEACTIVATE codes default to the system default if none is currently active. Default behavior is bolded where applicable.

Calling Feature Guide: (PDF)

Local Access Features

2+ Party
TypeDescriptionPer MonthPer Use
00NoYesYesUtilitySwitch Maintenance Mode
02NoPartialPartialPer-Call FeatureLine RecordACTIVATE
07YesYesYesPer-Call Feat.Call RedialFree
08NoYesYesFeature ToggleHold on HoldACTIVATE$1.25
09NoYesYesFeature ToggleHold on HoldDEACTIVATE
16YesYesYesFeature ToggleSecure CallingACTIVATE$0.75
17YesYesYesFeature ToggleSecure CallingDEACTIVATE
18YesYesYesPer-Call Feat.Secure CallingACTIVATE$0.75
19YesYesYesPer-Call Feat.Secure CallingDEACTIVATE
24NoYesYesUtilityIP Address Readback
25NoNoYesFeature ToggleCall WaitingACTIVATEVaries
27NoYesYesUtilityVisual ANAC
28NoYesYesUtilityCognitronics Automatic Number Announcement
29NoYesYesUtilityRingback Test
31NoYesYesUtilityWakeup Call$1.00
45NoNoYesFeature ToggleCall WaitingDEACTIVATE
47YesYesYesFeature ConfigNumber of RingsACTIVATE$0.25
49YesNoYesFeature ToggleLong Distance AlertACTIVATEDEACTIVATE$0.25
51NoYesYesRecent CallsWho Called Me?$1.25
57NoYesYesRecent CallsCall Trace$1.00
60NoYesYesFeature ConfigSelective Call RejectionACTIVATE$1.00
61NoNoYesFeature ConfigPriority Call (Sel/Dist Ring)ACTIVATE$1.00
63YesYesYesFeature ConfigSelective Call ForwardingACTIVATE$1.00
65YesYesYesFeature ToggleCaller Number DeliveryACTIVATEVaries
66YesYesYesPer-Call Feat.Repeat DialingACTIVATE$2.00
67YesYesYesPer-Call Feat.Caller Number Delivery BlockACTIVATE$0.50
68YesYesYesFeature ToggleCall Forwarding Busy/No Ans.ACTIVATEVaries
69NoYesYesRecent CallsLast Call ReturnACTIVATE$1.00
70YesNoYesPer-Call Feat.Call Waiting DisableACTIVATEFree
72YesYesYesFeature ToggleCall Forwarding VariableACTIVATEVaries
73YesYesYesFeature ToggleCall Forwarding VariableDEACTIVATE
74YesYesYesFeature ConfigSpeed Calling 8-CodeACTIVATEVaries
75YesYesYesFeature ConfigSpeed Calling 30-CodeACTIVATEVaries
77NoYesYesFeature ToggleAnonymous Call RejectionACTIVATE$1.00
78NoYesYesFeature ToggleDo Not DisturbACTIVATE$1.00
79NoYesYesFeature ToggleDo Not DisturbDEACTIVATE
80NoYesYesFeature ConfigSelective Call RejectionDEACTIVATE
81NoYesYesFeature ConfigPriority CallDEACTIVATE
82YesYesYesPer-Call Feat.Caller Number Delivery BlockDEACTIVATE
83YesYesYesFeature ConfigSelective Call ForwardingDEACTIVATE
85YesYesYesFeature ToggleCaller Number DeliveryDEACTIVATE
86YesYesYesPer-Call Feat.Repeat DialingDEACTIVATE
87NoYesYesFeature ToggleAnonymous Call RejectionDEACTIVATE
88YesYesYesFeature ToggleCall Forwarding Busy/No Ans.DEACTIVATE
90YesYesYesFeature ToggleCall Forwarding BusyACTIVATEVaries
91YesYesYesFeature ToggleCall Forwarding BusyDEACTIVATE
92YesYesYesFeature ToggleCall Forwarding No AnswerACTIVATEVaries
93YesYesYesFeature ToggleCall Forwarding No AnswerDEACTIVATE
94YesNoYesPer-Call Feat.Directed Call PickupACTIVATEVaries
95YesYesYesFeature ToggleLong Distance BlockACTIVATE$1.00
96YesYesYesFeature ToggleLong Distance BlockDEACTIVATE
97NoYesYesUtilityVoicemail General Selector
98NoYesYesUtilityVoicemail AccessVaries

Remote Access Features

These features may be controlled from any TouchTone® telephone. To use these features, dial the access number for your switch (this is not a free call, and the number will be different for each switch). The remote access number for NPSTNNA01 is 549-6591. When you dial the remote access number, dial your telephone number, then dial your NPSTN PIN, then dial the appropriate service code.

2+ Party
TypeDescriptionPer MonthPer Use
01YesNoYesFeature ConfigHotline configurationACTIVATEDEACTIVATE$1.50
72YesYesYesFeature ToggleRemote Call ForwardingACTIVATEVaries
73YesYesYesFeature ToggleRemote Call ForwardingDEACTIVATE

In addition to these features, feature codes 31, 51, and 98 are also available remotely.

Update Center Features

These features may be controlled from any TouchTone® telephone. To use these features, dial the access number for your switch (this is not a free call, and the number will be different for each switch). The remote access number for NPSTNNA01 is 549-8052. When you dial the remote access number, dial your telephone number, then dial your security code, then dial the appropriate service code. If you do not have a security code set yet, use your NSPTN PIN.

2+ Party
TypeDescriptionPer MonthPer Use
1YesYesYesFeature ToggleRemote Call ForwardingVaries
2NoYesYesFeature ToggleNo SolicitationVaries
3NoYesYesFeature ToggleSecurity ScreenVaries
4NoYesYesFeature ToggleCall RejectionVaries
5NoYesYesFeature ToggleSelective Call Fwd.Varies

Note that some services rely on other services to function as intended (e.g. Last Call Return and Who Called Me? both require Caller Number Delivery, and Number of Rings has no effect unless Call Forwarding No Answer is currently active).

Most custom calling features automatically activate or deactivate themselves if the appropriate code is dialed. Voicemail service must be provisioned for you, so callers who wish to have a voicemail box must call the Business Office to order the service. Voicemail service is provisioned on a separate number, with mailbox access provisioned on the requested line. A maximum of access to 5 mailboxes may be configured per line — voicemail charges are based on mailbox access, not mailbox existence or number of mailboxes. Call Forwarding Busy and/or Call Forwarding No Answer (or Call Forwarding Busy/No Answer) could be used to have a primary number automatically connect to your voicemail service if you are unavailable.

NA01 users only may also call-forward no answer calls (e.g. *92) to 231-0981 to George, the Interactive Answering Machine (individual machine per line). The Remote Access Code is your UCP PIN. Press 1 to listen to messages - then 1 to rewind 15s, 2 to fast-forward 15s, 3 to pause, 4 to skip to the first new message, and 7 to erase the tape.

Payphone Trunks

NPSTN has several coin and coinless payphone trunks for both single-slot and 3-slot payphones. These trunks are designed for use with payphones on regular loop-start ATA lines with no additional hardware configurations. In other words, they are designed for payphones that have no smarts in them and won't act like payphones on their own. For those with a non-COCOT payphone and just an ATA and few other resources, these trunks can be used to bring your payphone to life!

To use the trunk, have the your payphone immediately connect to one of the following numbers below, depending on which kind of trunk you want for your payphone. If you have an ATA on NPSTN, hosted by somebody else, you can have the ATA off-hook hotdial the number (e.g. (<:9500903>S0) — Grandstream users would specify { 9500903 } for the digit map and need to configure hotline dialing on the "Lines" tab). If you have an Asterisk switch, you should have a separate incoming context in extensions.conf for each kind of payphone trunk you want to use, which only allows calls to the the number below corresponding with that trunk (thus, the payphone trunk would be specified by the dialplan context specified in sip.conf). This is more secure, because a payphone caller will not be able to flash at payphone trunk dialtone and simply dial calls directly another way (or use a different kind of coin trunk, if all the coin trunks were dialable from that context). Linksys ATAs configured for hotline dialing may also prevent callers from flashing out of the payphone trunk, but Grandstream ATAs will not.

NOTE: NA01 users should use the hotline vertical service code instead of off-hook auto-dialing from an ATA.

The payphone trunks are fully integrated with the NPSTN toll ticketing (billing) system. The charges for payphone calls will always equal or exceed the charges that appear on your bill, so the coin revenue from a payphone on these payphone trunks will more than pay for the charges that appear on the bill for the line a payphone is on. Thus, you don't need to worry about charges. All you need to do is make sure your payphone sends proper 7-digit NPSTN caller ID (as it should anyways). Based on that, the payphone trunk will determine what rates are appropriate for calls made through the trunk. The charges will be very similar to calls made directly (i.e. not using a payphone trunk) from that number.

The local rate (initial deposit) on these payphone trunks is 10 cents. While information/directory assistance is free normally on NPSTN, it costs 25 cents from payphones.

The way payphone rates are determined is either a flat rate greater than any possible charge for a call of that type OR a specific rate rounded up to the nearest $0.05 based on the station rate charges to a number are assessed.

Payphone operators (automated) handle select calls (e.g. long-distance). Long-distance can generally be dialed direct at 112 (or dial 0 for local calls or ask for long-distance).

These trunks are designed to be 99% plug and play with ~ZERO configuration on the part of the payphone owner (a small adjunct circuit in the physical vicinity of the phone is needed to monitor the line for MF tones and apply +/- 130V to do collect and return; however, the trunks themselves work fine without such a circuit, you'll just have to make your phone operate "piggy bank style" and not be able to get coins returned). These trunks are the equivalent of the CO-type trunks used for payphones.

Trunk Types

950-0900    CHARGE-A-CALL NPSTN     COINLESS                                           TSPS	
950-0901    1-SLOT        NPSTN     PRE-PAY (COIN FIRST)                               112 OR COIN ZONE
950-0902    1-SLOT        NPSTN     POST-PAY (WECo, i.e. "Post-Pay")                   112 OR COIN ZONE
950-0903    1-SLOT        NPSTN     DIALTONE FIRST PRE-PAY                             112 OR COIN ZONE
950-0904    1-SLOT        NPSTN     ACTS                                               112 OR ACTS
905-0905    1-SLOT        NPSTN     DIALTONE FIRST FULL PRE-PAY                        112 OR ACTS
950-0902    1-SLOT        NPSTN     SEMI-POST-PAY (AECo, i.e. "Semi Post-Pay")         112 OR COIN ZONE
950-0911    3-SLOT        NPSTN     PRE-PAY (COIN FIRST) (GONG DETECTION)              COIN ZONE
950-1NPA    1-SLOT        PSTN      ACTS                                               ACTS

The Charge-A-Call trunk is designed specifically for Charge-A-Call and other coinless payphones. It is integrated with NPSTN TSPS and allows for seamless third-number billing. To use Charge-A-Call, as with the NSPTN toll-free number, you will need an NPSTN PIN, which can be set or reset in the NPSTN UCP. Calling card numbers are 12 digits, in the format NNXXXXXPPPPP, corresponding to the 7-digit phone number + 5-digit NPSTN PIN. The only difference is that instead of using # to dial sequence calls, dial ** instead.

1NPA is a PSTN trunk that offers outbound PSTN trunking. All you need to do is dial 950-1NPA, where NPA is the home NPA of your payphone, and PSTN calls will be billed based on the distance between called and calling NPAs (long-distance) or flat-rate (local). Local calls are 25 cents and long distance varies by distance and incurs an additional per-minute charge after the first 3 minutes. 0 and N11 are free, except for 411 from coin phones. Translations will handle N11 in each NPA.

WECo style plays a second dial tone upon callee answer, indicating coins should be deposited, whereas Automatic Electric muted the mouthpiece and DTMF pad until coins were deposited. The Automatic Electric post-pay trunk relies on the caller to determine whether or not he wishes to talk, or use the keypad, and thus pay for the call. If he doesn't, there is no charge for the call, but it is limited in duration to a reasonable ringback (between 1 and 3 minutes). If/when a coin is deposited, the caller will be able to speak and dial/use the keypad again. We recognize this will allow free calls to listen-only numbers (e.g. time and temperature numbers).

We recognize that it is possible to "phreak" all of the trunks above by using a redbox or similar device capable of reproducing ACTS tones and 3-slot bell gongs. Consequently, payphone trunk usage is limited to verified, legitimate NPSTN callers (e.g. CVS code 10). This is necessary because of the real possibility of toll fraud, and it is a choice deliberately made to minimize our losses. All coins deposited into payphones using the payphone trunks are kept by the payphone owner (like with a COCOT; we don't get any share in the profits, even though we're the "telephone company" — at the same time, we still foot the bill as the payphone provider. If you would like to donate to this project to offset our expenses, please contact us!

Payphone Advertisements & Instruction Cards

The following publications have been created for your convenience. They are actual size and ready to print. These are the recommended accessories for your NPSTN PSTN payphone:


Single Slot Lower Instruction Card:

Single Slot Upper Instruction Card:

Old Instruction Cards:

The following instruction cards are designed for use with semi post-pay coin phones. However, the PSTN trunks use ACTS, which is dial-tone first, not post-pay.

Single Slot Lower Instruction Card:

Single Slot Upper Instruction Card:

Project MF 2.0

All the awesomeness of the original Project MF… ported to modern Asterisk…

See the official Project MF 2.0 GitHub repo.


StepNet is an additional layer on top of NPSTN, not a separate network.

Using StepNet is very simple. When calling the dialnpstn subroutine, instead of calling it like so:
GoSub(dialnpstn,start,1(${EXTEN},disable,${zipcode})), call it like so:

Now, your call will use StepNet for that call!. Not quite. You will actually need to dial 118 on NPSTN, then dial your number, and it will tandem through StepNet and then terminate.

At this point, StepNet is still in development, so we recommend you keep all your subroutine calls using disable as ARG2 at this point. When StepNet is in beta or ready for public use, we will post updates here informing users that they can try it out.


The idea is to replicate the call routing of the 1950s into the NPSTN VoIP Network with a flag set in the API Lookup request. This flag can be disabled for normal peer-to-peer calling or enabled for peer-tandems-peer calling. Although more dialplan code will need to be developed for each Asterisk switch to actually implement StepNet trunking, the code needed to make the proper lookup request is already in the dialnpstn subroutine.

StepNet is an adjunct to, rather than a principle part of, each node's dialplan. Members are free to allow StepNet trunking or not at their node. The route table will keep track of what nodes have opted-in to StepNet trunking. You must opt-in to participate (a node is not a participant by default). Note by participant, any node can make a StepNet call without being a registered StepNet tandem, but only those who nodes who have opted-in will process (or trunk) the call along the way.

Once a caller makes a StepNet call by calling dialnpstn with ARG2 set to enable (as opposed to disable), the Central Route Table Controller then creates a physical route between the caller and the called party routing through physical tandems based on geographic areas in between the communications path. These tandems will be community-run, just like the end office switches.


Thoughts on StepNet from Brian Clancy, Senior Network Adviser

I see StepNet as an exciting project, fun for those who like to dial around one or more nodes, stack calls, etc. Without really having discussed this project yet I see it as something apart from the the local exchange network that NPSTN is currently. In the real world the trunk network and the local network are different parts of the telephone system albeit closely integrated. I see StepNet and NPSTN in the same way, two parts of the whole.

To my mind StepNet is the trunk network whereas NPSTN as is, is the local network. Thus I believe that StepNet should be a second dialplan at each node handling trunk connections between nodes and interfacing to the local network dialplan at each node. The local network node has junctions to its near neighbours whereas the trunk network node has direct or indirect access to every other node in the network.

Whereas local junction calls route via npstn@server my thinking is that trunk calls should route via stepnet@server thus allowing the dialplan at each node to be two separate entities with local links giving access between the two. There would be no 'subscribers' on the trunk network and in the local network there would be a distinction between local calls (home and adjacent nodes) and trunk calls making for more interesting call routing.

StepNet would be optional for those wishing to take advantage of its 'phreaking' possibilities and those less keen on such things would have access to all other nodes as if they were local and with CLID verification.

Words of Wisdom from Brian Clancy, Senior Network Adviser

Before I say more let me roughly explain how 4 wire transit working happened in the UK trunk network because my suspicion is that the US network worked in similar fashion.

Assume national number 052271299 was to be called. This number was actually 0+LC2+71+299 where 0 was the national access code, LC2 was the code for Lincoln GSC, 71 was the local code for Scampton exchange and 299 was the number on Scampton exchange. LC2 or 522 were known as the A,B and C digits and these were used to route the call from originating GSC to destination GSC via a maximum of one intermediate GSC. The ABC digits were used to select a route relay at the originating GSC which gave a route towards the destination GSC by stepping the local 4 wire switches. The 52271299 was then pulsed ahead to the next GSC, if this GSC was not the destination GSC then again the ABC digits would route the call as before to the next GSC which would be the destination GSC. At the destination GSC the C digit (2in this case) was used to determine which 10000 line exchange unit (there could be several such units controlled from/at a GSC where one unit was at the GSC and the others were dependent group switching centres, Lincoln GSC had its own unit and three dependents). At Lincoln GSC C digit selected Lincoln local exchange. Lincoln subscribers were on levels 2XXXX, 3XXXX, 4XXXX at that time while local Lincoln dependent exchanges were on 7X and 8X codes and 9X codes were local routes to adjacent GSCs and their dependent exchanges. So at Lincoln 71 was used to step the local selectors to seize a junction route to Scampton exchange.

Access to the national network from local exchange subscribers was by dialling the national access digit 0. The local exchange first selector recognised 0 and immediately, without stepping, seized a Local Register. Seizure of the local register sent a predetermined routing digit to the first selector (in my local exchange that digit was 9 but it could have been any digit) stepping it vertically to give a route to the group switching centre (GSC)and seize a register translator (R/T). In the meantime the subscriber dialled digits were stored and then pulsed out to the GSC to be stored in the R/T. The route to the next GSC was determined until the destination GSC was reached and the local code 71 was used to route to the local exchange and number 299 used to select the subscriber.

The key part of the above is obviously the trunk switching portion which was done with 4 wire amplified lines, AC9 (2280Hz signalling) Strowger (Motor Uniselector) and Crossbar 4 wire switching. The trunk portion of the call being set up using the same set of digits from originating GSC to destination GSC with or without an intermediate GSC. Modern digital switching uses similar routing principles whereby the same digits are used repeatedly to route a call before they are all spent.

Now in Asterisk it simply is not possible to replicate this type of call forwarding using forward and reverse handshaking so another method needs to be employed to simulate it. A lookup table would seem to be the obvious answer to the problem of forward routing. A node must have access to all adjacent nodes but it must also know which of those nodes is en route to a given destination so life immediately becomes more complicated. Each node must have at least two lookup tables, the first will be a list of all possible destinations which can be accessed from each adjacent node, the second will be the route access data to each adjacent node. Thus multiple tables would seem to be required, i.e. 10 nodes = 20 tables but a better way perhaps is to have a single destination table that lists all nodes with direct and indirect access to each destination and a second nodes table which lists each node and its directly adjacent nodes. That said the destination table could possibly be rationalised into say 5 sub areas which would reduce the amount of data duplication required and thus reduce lookup time.

Thus the calling extension would apparently need to have all the routing data needed to set up an end to end call before that call is dialled. Switching at the intermediate and destination nodes would need to be somewhat automatic at each node. There is a lot of thinking to be done in how to achieve this because you really wouldn't want to send 40 digits to setup a call through 5 nodes. Obviously what needs to be sent forward is enough information for a meaningful lookup of he next route leg. It would seem to be essential to retain the dialled number without 'spending' the digits en route as the dialled number would be the most obvious lookup data at every node of a call. The best way to look into this is perhaps to create a small model of say 5 nodes and 20 destinations, allowing two or three nodes in a route and then creating the example destination and node tables to see just how complex their population would become and what lookup difficulties would ensue.

Before you go any further my best suggestion is to establish the fundamental call setup principles first, prove them with basic lookup tables and a simple set of simulated nodes and routes. Forget all the clunks, clicks, stacking etc until you have a basic working model with an algorithm that is sound as a pound.

Commentary about StepNet from Brian Clancy, Senior Network Adviser

All the real work is in the database and I think I have shown that a 'known route' approach to lookups using one route table and one node table is the most efficient way to execute the task.

Dylan's idea of throwing in extra digits for routing is also a poor one. I have already mentioned that the industry used its routing digits carefully although I do not know the specifics of the NANP trunk network and I cannot be arsed to trawl through my full set of BSTJ documentation as I don't have an index for it. However, in the real world adding digits added complexity and incurred real additional costs for extra ranks of switches throughout the network. Editor's Note: The concept of "throwing in extra digits for routing" has since been discarded.

In the UK routing digits were limited to 6 maximum and clever methods of trunking and grading of electromechanical switching plant were used to get the most from the hardware. This did mean that some routes remained unavailable in the auto network until quite late in the day so trunk mechanisation took around 35 years to complete although the vast majority of the UK network was available in 25 years. I expect that the NANP made similar compromises while building the long distance automatic network in order to make best use of available funds.

Assume you want to place a call from say 2231234 to 9951234 and the route is via four intermediate exchanges, 444, 555, 666 and 777. The outgoing CLID(num) is 2221234.

The caller at 2231234 dials 9951234 and is routed to StepNet trunk network, this does a route table lookup which returns a route to 444 as the adjacent switching centre on the route to 995 and 9951234 is forwarded. At 444 a route table lookup for 995 results in a route to 555 and 9951234 is forwarded, and so on through 666 and 777 routing to destination 995 exchange where 1234 routes to the called party.

As you can see all the 'work' has to be in the route table, the route switching does not spend the dialled digits but simply forwards them to the next switching point in the route and the destination exchange spends the digits to call the local extension.

The routing table has to know how to route the call from origin to destination i.e. it has to know which of the possible switching centres with which it has connections is on the route to 995 from 223, indeed 444, 555, 666 and 777 will also need that information. None of them need to 'know' where the call originated as verification of CLID(num) at 223 can be confirmed by the addition of a suitable CLID(name) suffix at 223. There need be no subsequent CLID modification thereafter as the StepNet trunks will be secure password protected routes.

The concept is simple enough and does not differ from the one that Dylan floated when he first mentioned the StepNet project. The problem is the arrangement of data in the lookup table to allow efficient route lookup.

Assume 223 has access to half a dozen StepNet switching nodes, how will it know which one is on the route to 995? Here's a possible portion of the route table:
Node 223 has access to 227, 331, 444, 475, 626, 681
Node 227 has access to 229, 232, 561
Node 331 has access to 407, 409, 445, 479, 727, 731, 771, 802
Node 444 has access to 445, 477, 525, 555, 630
Node 475 .......etc
Node 555 has access to 525, 532, 666, 681
Node 666 ......................XXX, XXX, 777, XXX
Node 777 ......................XXX, XXX, XXX, XXX, 995

So 223 looks up it's own entry to find a switching centre to which it has access. It has to look at each of the six entries to see if any of them are on the route to 995 and then return an appropriate iaxuri and password for the route to 444. How can this be achieved? One way is to look at each entry in turn and search its entries in turn until a path is found via 444, 555, 666 and 777 to 995.

A better method is the 'two ends towards the middle' common control approach i.e. look at source and destination nodes searching through entries to reach a common node. Thus simultaneously searching from 223 and 995.

Both these methods are going to be very slow for more than three intermediate points and will involve some complex search algorithm nesting!

Of course the node doing the lookup is not interested in the data for the whole route, it only requires a path to the next node in the route.

There has to be a better way, which I will call the 'known routes approach'. This involves knowing all the routes in advance and having the intermediate points in a route lookup table entry as here:
From 223 To 388 Route 441, 472, 480, 398, 392
From 223 To 657 Route 444, 553, 592
From 223 To 995 Route 444, 555, 666, 777

The lookup node knows the origin node from the CLID(num) and destination from the dialled number so it can then look for the route. A 223 lookup for a route to 995 will find 444 as the first switching point in the list and return its iaxuri and route password from a separate node lookup table.

The lookup from 444 node for the 223 to 995 route will find itself as the first switching point in the list and then return the iaxuri for the next item, 555

This is a far more efficient method of routing on a per node basis.

The node lookup table will contain the iaxuri for the node and the password for the route.

Keeping things simple is always the best approach to my mind.

The lookup tables allow routes to be edited quickly and simply by changing database entries. The routes passwords should be centrally managed at the database, i.e. they will not appear anywhere in the dialplan at each node so the routes will be secure and automatically opened to genuine callers.


StepNet will make meaningful use out of each switch's geographic location. The zipcode global variable on each server will play an integral role in StepNet routing. Switches located in the United States should be sure their zipcode global variable is set to their location's ZIP code (you may wish to do this even if you have a hosted server), as per the NPSTN standards. Switches located abroad are considered "international tandems" in the StepNet hierarchy; the zipcode global variable should be set to 00000 as per the standards, which indicates the tandem is an international one. NPSTNMS0, the main NPSTN tandem, serves as the primary international routing tandem, switching calls between international nodes and domestic (U.S.) nodes. (Note, this is just for StepNet, which by its very nature is not peer-to-peer; normally, NPSTN calls are peer-to-peer no matter where the two nodes are located, international or not.)

It is currently undecided whether we will use IAX variables or not. If we do, another API will need to be created to populate the necessary IAX variable(s) initially and/or along the way.

Draft 1: Retired Conception

The following is an old proposal, but it is documented here for the sake of archival purposes:

Drafted by Dylan Cruz

This is an example of the call flow:

User at 330-1212 dials 646-1217:

That switch sends a HTTPS Request to

API Generates a route from Casselbery, FL (32707) to the end office in Miami, FL (33101)
API Generates a route to destination switch 3301212 (IAX2/[email protected]/3301212)
IAX2/[email protected]/06461217033012120998384001 <---- First Tandem in Winter Springs,FL (32708)
IAX2/[email protected]/06461217033012120998356002 <--- Second Tandem in Winter Park, FL (32922)
IAX2/[email protected]/06461217033012120998545003 <--- Third Tandem in Melbourne, FL (32901)
IAX2/[email protected]/3301212 <--- Final Switch in Miami, FL (33101)

All of the Tandems operated by community members will have a list of standards to follow, and a vintage sounds set to use.

So dialing 330-1212 in zip code 32707 will sound like this:

Party Answers

All decked out will line noise and bleeding sounds like 2600 Hz!

With MF tones and dial pulses it will be awesome; dialing a number will sound different to everyone based on their physical area!

Another thing about the Route Table handling StepNet calls: it checks each server before responding to the subscriber to make sure everything is A-OK, with automatic fail over!

Draft 2: Current Conception

Drafted by Naveen Albert

Here is an example of the call flow:

A caller at 330-1212 dials 231-6000. One way or another, ARG2 of the dialnpstn subroutine is set to enable (perhaps a special code is dialed for StepNet calls or perhaps this node has it always enabled, it's immaterial).

The lookup request that gets sent to the lookup API looks like this:${npstnkey}&lookup=2316000&cid=3301212&sntandem=enable&zipcode=32707

The API determines the route for the call, but only returns what the next immediate tandem is to the caller. Let's say the API lookup examines available routes (taking into account which nodes have opted into StepNet trunking and which have not) and decides the caller at 330 will be connected through 646, then 327, then finally connected to 231.

Note that it is the ZIP code that determines the routing, not any information about the exchanges themselves (like NNX). Thus, if the ZIP code were spoofed (perhaps for testing), this would result in a different routing that is geographically sensible.

For the sake of simplicity, let's assume the FQDNs and zip codes of each server are as follows:
330 - - 32707
646 - - 32901
327 - - 33101
231 - - 53189

Once 330 sends the lookup request above, the following will be returned:
IAX2/[email protected]/2316000

330 will establish a connection with 646.

646 will detect an incoming NPSTN call. It will verify the call and determine the call's CVS code is 10.

646 will see that the extension is 2316000, which does not exist on that node. It will know it is a StepNet call and process it accordingly by going to the [stepnet] context. Here, after playing an audio file or perhaps playing some in/outpulsing (MFs, etc.), it will ensure the caller is verified. If not, he is sent to a CBCAD intercept. But since he is, it will perform the following lookup:${npstnkey}&lookup=2316000&cid=3301212&sntandem=enable&zipcode=32901

The central route table will recognize the call and know to return the following:
IAX2/[email protected]/2316000

646 will establish a connection with 327, passing along the CVS code of 10 (as with any multiple-link call, StepNet or otherwise).

327 will detect an incoming NPSTN call. It will see that it was passed the CVS code of 10. It will verify the node passing it the call and determine the node is trusted. It will perpetuate the CVS code of 10.

327 will see that the extension is 2316000, which does not exist on that node. As before, it will be processed as a StepNet call. More outpulsing, clicks, bangs, etc. The node does not need to know which leg of the call it is since it will randomly choose to either play MFs, SFs, dial pulsing, a simple click, revertive pulsing, etc. (thus the need for an IAXVAR is essentially absolved). For more on the technicalities of this, see the contexts provided below.

Anyways, in due time, 327 will, after confirming the CVS code is 10 (if it were not, it would send the caller to CBCAD), perform the following lookup:${npstnkey}&lookup=2316000&cid=3301212&sntandem=enable&zipcode=33101

Consequently, the following will be returned:
IAX2/[email protected]/2316000

327 will establish a connection with 231.

231 will detect an incoming NPSTN call. It will see that it was passed the CVS code of 10 and, as before, determine the last node is trustworthy.

231 will see that the number does exist on that node, and it will terminate the call "as normal". 231 has no idea that this was a "StepNet" call since the number exists on that node; it is blissfully unaware of the routing and terminates the call, using its regular inpulsing subroutines.

And there you have it!

As of yet, the mechanism by which the route table would be able to keep track of each call is unknown. If it can be guaranteed that a particular lookup will return the same information at any particular time (i.e. a particular algorithim is followed as opposed to randomness), the API does not have to keep track. If 330 to 231 will always go through 646 and 327 first, and 646 to 231 will always go through 327 first, at least at the particular instant in time (i.e. the few seconds) that a call is taking place, then the route does not actually need to be centrally planned in advance, nor does it need to be kept track of, since it can be guaranteed that if the lookup request were done again, it would render the same result; wherefore it can be guaranteed that all StepNet calls will reach their intended destination with no "oversight", so to speak. It may also help to keep in the mind that the lookup number (calling party) and the cid number (called party) remain the same for each request; only the ZIP code differs. It will have to be determined how it will be handled if multiple nodes are in the same ZIP code (will it matter or not?)

Historically, the number of links maximum in the old network was 6 in the U.K. and 7 in the U.S. Therefore, we will limit the number of StepNet trunks to 7 maximum. Calls can, of course, complete using fewer than 7 trunks.

Footnote: A slight suggestion offered as an addendum is to have a separate IAX context called [sntandem] on each node to which StepNet calls would automatically be sent. This separate context will make it easier for nodes to accept StepNet calls and process them, and is capable of diverting calls that should terminate on that node to its regular incoming context.

An alternative approach bypassing the API is outlined in Discussion #3.

StepNet tandems are also used for automatic alternate routing by NPSTNNA01. If a direct trunk to another switch is not available, NPSTNNA01 attempts to establish the call by trunking through another StepNet tandem that may have a direct trunk. This only occurs when no direct routes are available.


All of the audio required for StepNet trunking is available here. StepNet-participating tandems will be expected to have all the following subroutines (and any prerequisites) on their nodes:

  • MFer
  • SFer
  • Dial Pulser
  • Revertive Pulser

These can all be found in the "Vintage Add-Ons" section of this documentation.

All other audio files needed for StepNet tandems are available as a standardized ZIP file downloadable below. The ZIP file contains all the ULAW files you need; no need for each node owner to convert them using SoX when we can do it once and also reduce the download size!

ZIP file containing standardized StepNet tandem audio files: click here to download.

Technically, if you don't do this, the StepNet code will automatically download files on demand. However, loading them onto your switch in advance will slightly speed up the first several dozen calls that tandem through your node.

Extract the audio files inside to the following location: /var/lib/asterisk/sounds/en/custom/stepnet/ (you will need to create the stepnet folder). The files are named sn1, sn2, sn3.

Dialplan Code

A dialplan code repository of approved, standard, and exceptionally fully documented StepNet code for members would also be a good idea. — Brian Clancy

Add the following to iax.conf


You will need to have a context called external-users in your dialplan. It should accept 7 digits and terminate numbers on your node appropriately.

The sndialtone variable should contain the path to the audio file containing the dial tone you would like used for your nodes StepNet DISA. The code for a node's StepNet DISA is already present in the below dialplan code, but you do not need to create a StepNet DISA on your node. If you don't plan to use it at all and don't reference, you technically don't need to create the above variable but it's still a good practice to do so. Because each node's dialtone file will not necessarily have the same path, the standardized code on each node simply includes the ${sndialtone} variable, and it's up to each node owner to define that in his or her [globals] context.

In case you might be wondering, our rationale for calling the variable sndialtone as opposed to simply dialtone was that this is such a common name for a variable that it's likely to already be used on some nodes for other purposes, and perhaps that dialtone may not be the one the node owner wishes to use for his StepNet DISA. You may find it redundant to define a second variable in this case, but it is done this way to explicitly avoid any possible conflicting usages.

The StepNet dialplan code itself is part of the eXtensible dialplan (as of December 2020), which makes StepNet maintenance much easier. If your node is not eXtensible, it is not recommended that you manually load the code onto your system, because StepNet is still in the beta phase and the codebase is not finalized yet.

That's it! To have StepNet calls tandem through your node, simply call the business office. In the future, there will likely be a UCP option that allows toggling this yourself.

Automatic Operators

NPSTN does a have a human operator who is frequently on-duty. When the operator lines are staffed, calls to operator services (i.e. 0, 411, 611, 555-1212) will be answered by an operator who can help you with long-distance calling, information/directory assistance, or troubleshooting your phone line. The proper numbers to call are documented in the "Numbering Plan" section of this documentation.

With the exception of the Business Office line, if the on-duty operator is, well, off-duty, or otherwise unavailable, calls will automatically be transferred to an automatic operator who will help you. The voice of these operators is our very own Brian Clancy. If you'd prefer, you can also dial an automatic operator directly, bypassing the regular network operator. Again, the proper numbers are documented in the "Numbering Plan" section.

The long-distance and information operators use speech recognition technology coupled with speech-to-text processing to power your request. Though not known for working well with a wide variety of voices, we're quite pleased with how well they work (it is perhaps a bit ironic that Brian Clancy, who has a British accent, sometimes has trouble getting "himself" to recognize himself when calling an automatic operator). Our speech-to-text processing is powered by IBM Watson (see the "Further Add-Ons" section for more on this).

The NPSTN and C*NET long-distance operators work as follows: certain phrases are translated to numbers, and the rest of the translation is discarded. This results in extremely accurate operator assistance. As long as you enunciate your number clearly, operator Brian will be able to place your call for you. In addition to using numerals (such as 0, 1, 2, etc.), you can also use exchange names. If all goes well, it will be interpreted as part of the number.

Our long-distance operators also support service inquiries. If you ask for the "time", you will be connected with POPCORN. If you say you have an emergency, or that you need the police, you will be connected with the NPSTN emergency intercept recording. If you ask for the temperature, you will be connected with 511. If you ask for time and temperature, you'll be connected with 211. If you ask for information, you'll be connected with Information. The automatic operator's high reliability is due, in part, to listening for and expecting only certain key phrases, and discarding the rest. Thus, we can expect a standardized response every time and can parse the request very accurately. Thus, if you say "Get me the police" or "Operator, could you get me the time and temperature, please?", the automatic operator will successfully be able to place your call since everything actionable is extracted and any extraneous text is discarded.

The Information operator, on the other hand, takes the reverse approach. Here, a caller could ask for any number of things that are in the NPSTN Directory. It is therefore not possible to listen for certain phrases and ignore the rest. Rather, we must do the opposite: detect certain likely phrases (such as "hello", "operator", "information", "please", etc.), discard them from the translation, and then do a lookup in the directory using a separate API. Hence, if the caller is extraneous with his words, unlike the regular long-distance operator, who will not mind at all, the Information agent may not be able to find what you're looking for, as extra words could be counted as part of the lookup. That being said, we have anticipated many of the most common phrases and words. For best results, however, we recommend being succint. "Hello, information? Could you get me the number for the muzak listen line, please?" is perfectly acceptable. "Hello, ouch, I just stubbed my toe, boy that hurt! Oh, hello? This is information, ain't it? Well what do you know! Might you by chance happen to have the number for the, uh, muzak listen line, perhaps?" is not. A bit far-fetched, perhaps, but hopefully you see our point.

Speech-to-text processing is not done for the automatic repair agent. Instead, the repairman will collect your information and, at a later point in time, your inquiry will be reviewed by an NPSTN service technician. To expedite a ticket, you can try calling our business office during regular hours.

Again, all the numbers described in this section are available in the "Numbering Plan" section of this documentation.



NPSTN provides free CNAM dips for NPSTN numbers via the authenticated CNAM API:${npstnkey}&number=2311111

The strict argument will truncate CNAM after 15 characters, so as to remain compatible with telephone terminal equipment that does not expect more than 15 characters, e.g.:${npstnkey}&strict&number=2311111


NPSTN offers complimentary paging services to email endpoints, including SMS.

Please call the Business Office for a free pager number.


NPSTN has a complimentary telegram service. Telegrams can be ordered by filling out an electronic telegram blank or by calling the chief telegraphist at 117. Telegrams are delivered electronically by email as well as telephonically and are available anytime in the NPSTN UCP. There is no charge for these functions, and delivery by these methods is guaranteed. Additionally, senders can opt to have their telegram delivered physically as a Mailgram using the USPS. There is a 49-cent charge for this. Payment must be sent by check or cash. Further information is available with the chief telegraphist.

Telegrams are sent in American Morse Code (landline Morse), not International Morse (CW) as they were originally. If you have a Morse sounder, you can connect it your phone line using a Tandy DCM6 modem and a custom serial DB9 cable as follows:

9 pin female - 25 pin male - MorseKOB Use - RS-232 signal ID (9 pin - 25 pin)
pin 5 - pin 7 - Ground - (SG - SG)
pin 6 - pin 3 - Key signal from modem to MorseKOB - (DSR - RXD)
pin 7 - pin 2 - Sounder signal from MorseKOB to Modem - (RTS - TXD)

A conventional modem will not work, because pure tones must be passed through with no "smarts" or specified baud rate. A conventional serial cable will also not work.

You can then connect your phone line to MorseKOB to sound out the telegram using your computer's audio, or you can connect it directly to a telegraph sender. To connect it to a sounder and key, this terminal interface is required.

If you don't have the appropriate modem or cable, you will not be able to receive the Morse code, but the telegram is also read aloud to you afterwards.

While USPS has increased postage from 49 cents to 55 cents, the charge is still 49 cents. NPSTN plans to raise the charge to 70 cents in the future, in order to cover postage and associated mailing costs, but this motion has not yet gone into effect.

Wake Up Calls

You can schedule one-time wake-up calls or recurring, snoozable wakeup calls. One-time wakeup calls can be scheduled at LIncoln9-1414.

Recurring wakeup calls must be scheduled through the NPSTN Business Office or operator. Once scheduled, a recurring wakeup call will call you every day at the same time. You will be prompted to input the answer to a relatively simple arithmetic computation. Until you get it right, it will keep calling you back every 6 minutes, to ensure that you don't go back to sleep! If you don't answer or enter the wrong answer continually and/or hangup, it will keep trying to reach you. If you do not pick up at all that day, your recurring wakeup call will be automatically cancelled, and you will need to reschedule it. Recurring wakeup calls can be cancelled either by contacting the Business Office or not answering wakeup calls for 1 day. The wakeup call system will try to reach you for 2 hours each day; if the calls are not answered by 2 hours, your recurring wakeup call will be cancelled. This means if you go out of town and forget to suspend your wakeup call, it will be automatically cancelled for you after the first day.

Toll Free Number

NPSTN has a toll-free access number that can be used to call NPSTN numbers or send a telegram. Its primary use is to replace collect calls from payphones for members stranded without change. More information about the toll-free number can be found upon logging in to the UCP.

To use the toll-free number, you will need to enter an NPSTN calling card number. NPSTN calling card numbers are simply your 7-digit phone number + your 5-digit NPSTN PIN, which can be set in the NPSTN UCP. The phone number used must belong to you and must be in the White Pages (though it may be unlisted or private). Thus, the format of calling cards on NPSTN is NNXXXXXPPPPP, where "P" represents any numeric digit.


SIT-COM, short for SIT Communications, is a free 2-way communications system that can be used at most COCOTs. It is targeted towards payphone users who frequently communicate tidbits of common information and would like to do so at no charge. More information about SIT-COM is available to logged in members.

Blue Box

To access the NPSTN "blue box", you must access a DISA tandem trunk on NPSTNNA01.

Press the "*" key to drop the dial tone and drop into a "2600 Hz trunk".

The * key serves as KP; the # serves as ST. You must dial KP + # + ST.

Access to these trunks is highly restricted and controlled. All access is logged. The trunk allows access to special areas of NPSTN that are not dialable directly, including TTY lines, toll-free lines, certain PSTN lines and access trunks, inward operators, special directory services, and verification trunks.

For security reasons, public access to these trunks is no longer allowed. Their use is restricted to network operators only.

NPSTN System Practices

The NPSTN System Practices are a collection of documents modeled after the Bell System Practices. Each practice or specification contains information on a specific aspect of the network, beyond the level of detail provided in this documentation. Some documents are classified or intended for internal usage only. These documents are the property of NPSTN are not for use or circulation outside of NPSTN.

NSPs are circulated for internal use only. The latest copies are available through the Network Operations Center.

C*NET Connectivity

First, you will need to add the following to your iax.conf:


This assumes that cnet is your C*NET IAX username; adjust this accordingly if you already a C*NET IAX username that is different.

The approach you will adopt to handle C*NET calls will differ based on whether you want to provide access to the same extensions on both networks or not. In the easiest case, if your US C*NET office code matches your NPSTN office code, so that the only difference between the two numbers on both networks is that your C*NET numbers have a 1 before them, the following will be sufficient:

exten => _X!,1,GoSub(cnet-verify,${EXTEN},1)
	same => n,Log(NOTICE, Incoming call from C*NET: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,GoTo(external-users,${EXTEN:-7},1) ; strip leading 1 from received number

As you can see, the last line assumes you are using the same numbers on both networks. Change the destination context if you want to have different numbers for NPSTN and C*NET. While using the same numbers may be easier, differentiating between the two networks makes your node more unique.

You will need to register for a C*NET office code if you don't already have one. You'll need to do this with C*NET, with whom we are not affiliated. All the instructions you need to get setup are on the C*NET website.

To dial out, you will need the official [dialcnet] macro, developed by, once again, Brian Clancy. The code is also included below for your convenience:

[macro-dialcnet]                                        ;version bjc1.01 - asterisk1.4+ and asterisk 1.2
exten => s,1,Set(NUMBER=${ARG1})                       ;Store number to be called
exten => s,2,GotoIf($[ ${ARG1:0:1} = "+"]?search)       ;Is number prefixedwith '+'?
exten => s,3,Set(ARG1=+${ARG1})         ;Prefix number with '+',required toproperly retrieve US ENUM entries!!!
exten => s,4(search),Set(ENUM=${ENUMLOOKUP(${ARG1},ALL,,1,}) ;Search ENUM database (Asterisk 1.4+)
exten => s,5,GotoIf($[${LEN(${ENUM})}=0]?no_uri)        ;Is ENUM record found?
exten => s,6,Gotoif(${DB_EXISTS(ELBD/${NUMBER})}?cdata) ;Does backup entry exist?
exten => s,7,Set(DB(ELBD/${NUMBER})=${ENUM})            ;No existing backup so store backup ENUM data
exten => s,8(test),GotoIf($[${ENUM:0:3} = iax ]?iaxuri) ;Yes-IAX2 protocol
exten => s,9,GotoIf($[${ENUM:0:3} = sip ]?sipuri)       ;Yes-SIP protocol
exten => s,10,GotoIf($[${ENUM:0:3} = h32 ]?h323uri)      ;Yes-H323 protocol
exten => s,11(no_uri),Gotoif(${DB_EXISTS(ELBD/${NUMBER})}?seek)  ;ENUM="", is there a backup entry?
exten => s,12,Macro(invalid-office-code,${NUMBER})               ;No valid ENUM and no backup entry
exten => s,13,Wait(5)                                    ;Pause
exten => s,14,Hangup                                     ;Done - failed to make call - Goodbye
exten => s,15(seek),Set(ENUM=${DB_RESULT})               ;Set ENUM to backup base entry
exten => s,16,GotoIf(${REGEX("iax2,sip,h323"${ENUM})}?test)      ;If it's a single backup proceed to dial out
exten => s,17,Set(NE=${CUT(ENUM,":",1)})                 ;Get backup entry field 1
exten => s,18,Set(LU=${CUT(ENUM,":",2)})                 ;Get backup entry field 2
exten => s,19,GotoIf($[${NE}>${LU}]?grec)                ;Was last used the last entry in list?
exten => s,20(frec),Set(LU=0)                            ;Reset entry pointer
exten => s,21(grec),Set(LU=$[${LU}+1])                   ;Increment last used pointer
exten => s,22,Set(ENUM=${DB(ELBD/${NUMBER}/${LU})})      ;Read ENUM backup entry
exten => s,23,Set(DB(ELBD/${NUMBER})=${NE}:${LU})        ;Update last used record
exten => s,24,GoTo(test)                                 ;Proceed to dial out
exten => s,25(iaxuri),Set(DIALSTR=IAX2/${ENUM:5})        ;IAX2
exten => s,26,GoTo(dodial)                               ;Make call
exten => s,27(sipuri),Set(DIALSTR=SIP/${ENUM:4})         ;SIP
exten => s,28,GoTo(dodial)                               ;Make call
exten => s,29(h323uri),Set(DIALSTR=H323/${ENUM:5})       ;H323
exten => s,30,Macro(invalid,${NUMBER})                   ;Make Call
exten => s,31(dodial),NoOp(Outbound Caller ID is ${CALLERID(all)})
;exten => s,31(dodial),Set(CALLERID(all)= Brian Clancy - PBX6 < 4418072345 >)
exten => s,32,Dial(${DIALSTR})                           ;Dial Out
exten => s,33,Hangup                                     ;Done -call attempted - Goodbye
exten => s,34(cdata),Gotoif($["${DB_RESULT}"="${ENUM}"]?test)    ;Does entry have a single backup?
exten => s,35,Set(SR=${DB_RESULT})                               ;Backup does not match current
exten => s,36,GotoIf(${REGEX("iax2,sip,h323"${SR})}?shuffle)     ;Is more than one backup entry stored?
exten => s,37,Set(NE=${CUT(SR,":",1)})                   ;Get backup entry field 1
exten => s,38,Set(LU=${CUT(SR,":",2)})                   ;Get backup entry field 2
exten => s,39,Gosub(rent)                                ;Check multiple backup entries
exten => s,40,Set(DB(ELBD/${NUMBER})=${NE}:${MR})        ;Store no. of entries and match as entry last used
exten => s,41,GotoIf($[${MR}>0]?test)                    ;If backup exists proceed to dial out
exten => s,42,Set(NE=$[${NE}+1])                         ;Increment entries pointer
exten => s,43,Set(DB(ELBD/${NUMBER})=${NE}:${NE})        ;Update entry count and usage
exten => s,44,Set(DB(ELBD/${NUMBER}/${NE})=${ENUM})      ;Store nth backup entry
exten => s,45,GoTo(test)                                 ;Nth entry stored - proceed to dial out
exten => s,46(shuffle),Set(DB(ELBD/${NUMBER}/1)=${SR})   ;Move stored entry to backup entry /1
exten => s,47,Set(DB(ELBD/${NUMBER}/2)=${ENUM})          ;Create second backup entry /2
exten => s,48,Set(DB(ELBD/${NUMBER})=2:2)                ;Store no. of backup entries & last called
exten => s,49,GoTo(test)                                 ;Alternative backup entry stored - proceed to dial out
exten => s,50(rent),Set(i=0)                             ;Load test counter
exten => s,51,Set(MR=0)                                  ;Load match store
exten => s,52,While($[${i}<${NE}])                       ;Check multiple entries for a NUMBER looking for a match
exten => s,53,GotoIf($["${DB(ELBD/${NUMBER}/$[${i}+1])}"="${ENUM}"]?mark)
;Mark entry matching ENUM result
exten => s,54,Set(i=$[${i}+1])                           ;Increment counter
exten => s,55,EndWhile                                   ;Look at next entry if one exists
exten => s,56,Return                                     ;Done searching for ENUM result backup record
exten => s,57(mark),Set(MR=$[${i}+1])                    ;ENUM result already backed up
exten => s,58,Set(i=${NE})                               ;Load counter with no. of last entry
exten => s,59,ContinueWhile                              ;Stop searching

exten => s,1,Playback(custom/switch/ccad,noanswer)

The version above caches ENUM lookups so that, if the ENUM lookup were ever to become unavailable, you would still be able to reach destinations to which you previously placed calls successfully. Here is an updated adapted and simplified version of the C*NET macro that is, in fact, not a macro but a subroutine, and does not cache ENUM lookups:

[dialcnet] ; CNET NA 20190219, adapted from BJC v1.01
exten => s,1,Set(NUMBER=${ARG1})
	same => n,GotoIf($[ ${NUMBER:0:1} = "+"]?search)
	same => n,Set(NUMBER=+${NUMBER})
	same => n(search),Set(ENUM=${ENUMLOOKUP(${NUMBER},ALL,,1,}) ; Asterisk 1.4+
	same => n,GotoIf($[${LEN(${ENUM})}=0]?no_uri)
	same => n,GotoIf($["${ENUM:0:3}"="iax"]?iax)
	same => n,GotoIf($["${ENUM:0:3}"="sip"]?sip)
	same => n,GotoIf($["${ENUM:0:3}"="h32"]?h323)
	same => n(no_uri),GoSub(invalidcnet,s,1)
	same => n,Return
	same => n(iax),Set(DIALSTR=IAX2/${ENUM:5})
	same => n,GoTo(dialstr)
	same => n(sip),Set(DIALSTR=SIP/${ENUM:4})
	same => n,GoTo(dialstr)
	same => n(h323),Set(DIALSTR=H323/${ENUM:5})
	same => n(dialstr),Dial(${DIALSTR},,g)
	same => n,Return()

exten => s,1,Playback(custom/ccad,noanswer) ; CCBCAD
	same => n,Return()

You will need to adjust invalidcnet to play your local CBCAD intercept.

You can call the above subroutine with the following syntax:

exten => _1NXXXXXX,1,GoSub(dialcnet,s,1(${EXTEN}))
	same => n,Hangup()
exten => _011X.,1,GoSub(dialcnet,s,1(${EXTEN:3}))
	same => n,Hangup()

PSTN Connectivity

Incoming Calls



Interested in a free DID? IPComms will give you one! It will even come with two incoming channels that allow unlimited incoming minutes (with the restriction that you can only have 2 incoming calls at a time). The only catch is the number is randomly assigned to you; the area code in which your DID is located is completely random as well. You can sign up with IPComms on its website. The signup link takes you to a social media page, but you don't need to be a registered member there; that's merely where the form is located.

Note that to get your free DID setup, you will receive a call from IPComms during business hours. This goes without saying, but make sure your number is accurate!

They will then ask you for some basic information: full name and email address.

Once they hang up, you will receive an email later with your login information — you will need this!

Required Contexts

Unlike NPSTN and C*NET, the vast majority of commercial PSTN offerings use SIP trunking as opposed to IAX trunking. Open up sip.conf and add the following statement in your [general] context:

register => 2125551212:[email protected]/ipcomms

The phone number goes first, followed by a colon and then your password. The rest is pretty self-explanatory. The very last "ipcomms" at the end after the slash is the destination SIP context, which you will add below like so:


That's all the SIP configuration needed! At the Asterisk CLI, type sip reload to reload SIP and register with IPComms.

Now, calls from IPComms will come into your Asterisk switch, but they still need to be routed. Add the following to extensions.conf:

[from-ipcomms] ; This is the incoming context for IPComms SIP calls
exten => 2125551212,1,Progress()
	same => n,Set(Var_TO=${CUT(CUT(SIP_HEADER(To),@,1),:,2)})
	same => n,GoSub(pstn-us-verify,s,1)
	same => n,Log(NOTICE, Incoming call from IPComms PSTN ${Var_TO} DID: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" via ${EXTEN})
	same => n,Answer()
	same => n,SendDTMF(1) ; for Google Voice
	same => n,GoSub(mfer,start,1(2125551212))
	same => n,GoTo(from-pstn,s,1)

exten => s,1,GoTo(dt,DTXB,1)

First, change the number of the extension to your 10-digit DID.

Since we're seeing 2125551212 in a couple places, you may want to define a global variable called IPCOMMSDID1 in your [globals] context and use that in lieu of the number itself throughout your dialplan. However, you won't be using the DID itself very much. Unlike calls from NPSTN and C*NET, calls to your DID are all coming to the "same extension". Your internal extensions can't be dialed directly. Thus, you'll need to have this go to your node's DISA if you want external callers to be able to reach a specific destination of their choosing; your extensions can't be directly dialed from the PSTN. (This is because you only have 1 PSTN; if you have an NNX-X on NPSTN or C*NET, you have 1,000 DIDs!)

The second and third-to-last lines here are optional and can be omitted. The SendDTMF is necessary if you have a Google Voice number forwarding to one of your DIDs. This is not an IPComms thing at all; due to the way answer supervision works with Google, you will usually need to provide progress, wait a few seconds (about 4), then Answer it, then send DTMF 1. This context omits the Wait statement, but you may need to add that if Google Voice calls forwarded to your IPComms DID aren't getting answered properly by Asterisk.

Finally, the mfer subroutine MFs your IPComms DID. This is a slight nostalgic touch; while we think it's a nice addition for PSTN callers to hear on their way in, you can omit this (and certainly should if you don't have the required subroutines, which are available elsewhere in this documentation).

Finally, we have chosen here to send the caller to the [from-pstn] context, which in turn directs the caller to the node's DISA (you may need to adjust the extension from DTXB to the one your DISA uses). We have a separate [from-pstn] context for the simple reason that you can have multiple DIDs, each of which should have its own incoming context as the processing needed initially is slightly different. However, you can then send callers to a common PSTN context and from there onward to wherever you'd like them to go. This way, if you decide you'd like to have PSTN callers enter, say, an IVR instead of a DISA, you only need to change the reference in [from-pstn], as opposed to within each of your DID contexts.



CallCentric used to have a free DID plan that offered 2 numbers and 3 incoming channels each with unlimited usage. Unfortunately, they discontinued it in December 2018 for new users and for existing ones in February 2019. However, they still do have a (quite reasonable) $1 DID plan that gives you 1 DID with 2 incoming channels. While that means you only get 2 incoming channels now, instead of 6, and you have to pay for it, it's not by any means a bad deal!

If interested, register with CallCentric. If you opt for the $1 per month DID, select their "Dollar Unlimited" plan. Just like with the old "Free DID" plan, numbers are from New York state area codes 631, 845, and 914. Unlike IPComms, you do get to choose the area code you want; however, the actual number itself is still randomly assigned to you.

Note that CallCentric has a 911 requirement for users located in the US. If you don't need 911 on your switch, you will need to indicate when registering, that you are not located in the United States. Otherwise, you will be forced to pay an additional free for 911 service. If you do need 911 service, make sure to provide accurate location information.

Required Contexts

You will need to add the following line to your [general] context in sip.conf:

register => 17775551212:[email protected]/callcentric

First is your 11-digit internal CallCentric number. This is not the number of your DID, if you have one! Each account is assigned an internal CallCentric extension in the pseudo-area code 777. This allows you to dial other CallCentric users for free (should you want to).

Now, beneath all that, add the following to sip.conf:

























Yes, unfortunately, you really do need all of that! You can see more about this on CallCentric's Asterisk configuration page.

Now, in extensions.conf, add the following:

[from-callcentric] ; This is the incoming context for CallCentric SIP calls
exten => callcentric,1,Progress()
	same => n,Set(Var_TO=${CUT(CUT(SIP_HEADER(To),@,1),:,2)})
	same => n,GoSub(pstn-us-verify,s,1)
	same => n,Log(NOTICE, Incoming call from CallCentric PSTN ${Var_TO} DID: "${clidverif}" ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,Wait(4)
	same => n,Answer()
	same => n,SendDTMF(1)
	same => n,GoSub(mfer,start,1(2125551212))
	same => n,GoTo(from-pstn,s,1)

Again, if you want to use the MFer to MF your DID's number on incoming PSTN calls (i.e. inpulsing), you will need to change the argument to reflect your CallCentric DID's number. Otherwise, the same comments about SendDTMF, etc. as with the IPComms configuration apply here also.

For those curious, Var_TO here contains your DID's number, which allows you to send different DIDs to different contexts if you have multiple.

Outgoing Calls


NerdVittles is currently sponsoring a Skyetel promotion. They are offering $50 of credit for anyone interested in trying the service. Here's how to take advantage of it on your NPSTN server!

Why are we recommending Skyetel? Well, apart from the fact that they're offering $50 free credit to us NerdVittles geeks, the service is extremely reliable and we think you'll really like it! It's fast and easy to use. Their customer service is really the best in the industry (we're serious, instant chats are really instant and realtime, and contacting support has about a 5 minute turnaround or less, on average!).

A few tips before getting started:

  1. You will want to use your Skyetel trunk only for outgoing PSTN calls! There are companies that offer DIDs that are free or very cheap, which will provide you with unlimited free incoming PSTN calls. All Skyetel calls (incoming and outgoing) are charged and will use credit! Don't waste your minutes on incoming calls. We will help you set up a message that instructs callers to hang up and dial one of your Free DIDs. Skyetel allows you to set outgoing caller ID, so you may choose to set it to that of your free incoming DIDs as well.
  2. Toll-free calls are free (this isn't listed in the rates), but don't make test calls using this service and don't use it to war-dial! You will get charged extra for calls that are extremely short!
  3. Ensure you having meaningful caller ID sent out. By default, it is whatever it is when you reach the Dial statement. No verification is done and your call will go through, but you will be billed extra if your caller ID is blatantly meaningless (i.e. 0 or 1212 as opposed to an 11 digit US PSTN number).
  4. Don't get more than one phone number. You only get $50 of credit, and each number is a $1 one-time purchase, plus $1 per month. You won't be giving your Skyetel number out to anybody anyways. No point in wasting your credit in getting unnecessary numbers. You can use Skyetel to place an UNLIMITED amount of calls (there is no channel limit) using your number; only minute-by-minute usage is counted and billed.

So, what's the catch? There must be one, right?

Of course there are! Two, really!

  1. You need a valid US mobile phone number to verify your account. Google Voice numbers don't work. Not everyone has or wants a mobile, so this could be a problem for some. You don't need it afterward, though, so you can always ask a friend, as it's a one-time need.
  2. You need a debit or credit card to verify as well. Nothing is billed, but you do need a valid card. If you don't have one, you can also use a bank savings card or temporary card — i.e. use cash to buy a $5 VISA gift card and use that. Don't worry; auto-billing is disabled by default and you can remove your card immediately afterward.
NerdVittles Promotion
  1. Since only NerdVittles readers get this offer, not just anyone signing up for Skyetel, you need to fill out this prequalification form. When you sign up for Skyetel in a second and ask for your account to be credited, they'll make sure your information is entered in here. You must accurately enter all info. It must match the Skyetel signup form, which verifies everything.
  2. Sign up for Skyetel. You'll see a special link to do this once you complete step #1. Enter in all the same info as in Step #1. If the email address doesn't work, try a different one. Again, mobile numbers here will not work for verification; keep that in mind. You'll also need to enter your debit/credit card # at this point.
  3. Contact support. Once you're logged into your account, in the vertical menu, you'll see "Get Support" at the very bottom. Explain you are using the NerdVittles promotion so they know to credit your account with $50.
Skyetel Configuration
  1. In the vertical menu, go to "Phone Numbers" and find a number. You can search by NPA-NXX. It can be in your NPA or not, your choice. Search around for one you like, as this is what your outgoing caller ID should be.
  2. Once you've bought a phone number, go to the number's Settings, then go to Features. Disable "Caller ID" and "Block Spam Calls". Why? These features cost extra, so turn them off so you can save as much as possible for your outgoing minutes. None of the features should be active, like so:
  3. In the same dialog, go to "General" and change SIP Format to the second option, 1NPANXXXXXX.
  4. Now, you'll need to set the Endpoint Group. In the vertical menu, choose "Endpoints", which is right above "Phone Numbers".
  5. When the submenu for Endpoints pops up, choose "Endpoint Groups".
  6. Click "Add Endpoint Group".
  7. Fill in the text fields with the right information, using "skyetel1" as the Name and Description, as shown:
  8. For IP address, enter your Asterisk server's public IP address. For port, enter your SIP port. If you have changed your bindport in sip.conf from the default, you need to use that port.
  9. Click "Add".
  10. Now, go back to "Phone Numbers" → "Local Numbers" and edit the settings of the number you bought. Set the endpoint group to "skyetel1" like so:
Required Contexts

Add the following to sip.conf:


Now, on to extensions.conf!

Now, in order to dial out using Skyetel, add the following to your [internal-users] context:

exten => _1NXXXXXXXXX,1,Dial(SIP/SkyetelTERM/${EXTEN},45)
	same => n,Dial(SIP/SkyetelOR/${EXTEN},45)
	same => n,Dial(SIP/SkyetelCA/${EXTEN},45)
	same => n,Dial(SIP/SkyetelVA/${EXTEN},45)
	same => n,Hangup()

That's it! Now, you have over 50 hours of free long-distance at your disposal!

Google Voice

Instructions exist for implementing Google Voice on IncrediblePBX; however, this guide will target vanilla Asterisk users since instructions targeting native Asterisk are virtually nonexistent. Implementing Google Voice on vanilla Asterisk can be done, but we ran into many roadblocks along the way. The process is rather complicated, so buckle up!

PJSIP Dependencies

Google Voice requires the use of PJSIP, rather than SIP. You will need to install or reinstall Asterisk with the proper PJSIP modules already on your system. Fortunately, this is not as complicated as it sounds.

The first step is making sure PJSIP is installed as per the first part of these instructions. This assumes you have Asterisk 13.8 or later on your system.

  1. Navigate to your Asterisk source directory. If you don't know what it is verbatim, you can find it by typing the following:
    cd /usr/src
  2. Now, enter the Asterisk source directory, e.g.
    cd asterisk-13.24.0
  3. Now, enter the following (you may need to add " install" without quotes after if it prompts you to):
  4. Next, enter the following commands:
    ./configure --with-pjproject-bundled
    make && make install

Okay, now the tricky stuff is done! (Well, some of it anyways.) You don't need to copy any files over to your switch again. None of the configuration files or audio files were modified. However, Asterisk now has the PJSIP dependencies it needs loaded.

Google Voice Installation
Part 1

You will need to configure tokens for use with Asterisk. Navigate to this page; start at "Configuring GV Trunk with Motif in the GUI" to generate the oauth refresh token only. The script is preset to use the guide's Client ID; if you want to use your own then you will need to edit the script.

Part 2

Run the following from the shell:

cd /root
tar xvf gvsip-naf.tar
rm -f gvsip-naf.tar
cd gvsip-naf

This does take a while to install; don't worry!

You should at some point be prompted now to enter your Google Voice information; do so as instructed.

Part 3


sed -i 's|||' /etc/asterisk/pjsip_custom.conf
sed -i 's|||' /root/gvsip-naf/install-gvsip
asterisk -rx "core restart when convenient"

Helpful Resources:

Part 4

Up until now, these instructions have all been IncrediblePBX instructions that work without any hitches on vanilla Asterisk. The rest of this tutorial is more specific to vanilla Asterisk.

Required Contexts

Further Add-Ons

[public] context

Most spam calls to Asterisk systems are directed at the [public] context. Consequently, here is a relatively novel (but incredibly simple) addition to your dialplan that can significantly cut back on spam calls to your switch:

exten => _X!,1,Log(NOTICE, Unauthorized call: ${CALLERID(all)} at "${CHANNEL(peerip)}" to ${EXTEN})
	same => n,System(iptables -A INPUT -s ${CHANNEL(peerip)} -j DROP)
	same => n,Hangup()

You shouldn't intentionally use the [public] context for anything, so it's a pretty safe bet to assume that any call arriving at that context is malicious. If you add the above to your dialplan, once a spam caller makes a call to any extension in your [public] context, he will be banned from your server forever — well, at least until the next time it reboots (see the iptables section in Appendix A for more on this).

Of course, this context in extensions.conf alone is not enough. You will need to create a couple iax contexts in iax.conffor public calls as well, like so:


callerid="Guest IAX User"

Many spam calls arrive at the [guest] iax context, so make sure you include both of these. While IAX spam (or SPIT, spam over Internet telephony) is nowhere near as common as SIP spam, if you do get any spam calls, these contexts should take care of them. If you used our starter code for sip.conf in the "Getting Started" section, your server is automatically setup to reject unauthenticated SIP calls.

Previously, calls to your [public] or [guest] iax context would have been rejected by your server as being nonexistent. Now, the calls will be accepted, and the IP address of the server originating the call will be blocked with iptables. Neat! Of course, although most node owners will want to block calls such as these, you have no obligation to do so. One novel use for spam calls on NPSTN is using them for crosstalk. The MIlton6 exchange, rather than banning IP addresses who make spam calls, adds incoming spam channels to a connection that allows live, unique crosstalk to be generated. Talk about novel! (If you're interested in this concept, see the "Crosstalk" section in Vintage Add-Ons.)


In addition to using the switchhook to flash to initiate a 3-way call, you can use Asterisk's 3-way calling capabilities (as opposed to your telephone's) to solve this:

Set the following in features.conf:

atxfernoanswertimeout = 60     ; Timeout for answer on attended transfer default is 15 seconds.
atxferabort = *1               ; cancel the attended transfer
atxfercomplete = *2            ; complete the attended transfer, dropping out of the call
atxferthreeway = *3            ; complete the attended transfer, but stay in the call. This will turn the call into a multi-party bridge
atxferswap = *4                ; swap to the other party. Once an attended transfer has begun, this option may be used multiple times

blindxfer => #1                ; Blind transfer  (default is #) -- Make sure to set the T and/or t option in the Dial() or Queue() app call!
disconnect => *0               ; Disconnect  (default is *) -- Make sure to set the H and/or h option in the Dial() or Queue() app call!
atxfer => *2                   ; Attended transfer  -- Make sure to set the T and/or t option in the Dial() or Queue()  app call!

Now, while in a call, you can press *2 to initiate an attended transfer. If you three-way a call in, you can use *0 to kick it out, provided you add the appropriate Dial() options.

Extending Your System: Shell Scripts

Custom shell scripts can greatly extend the power of Asterisk. For example, time and temperature scripts allow us to run the time and temperature services on NPSTN. You can do an infinite number of things with shell scripts that interface with Asterisk.

Windows & Unix Encoding

When modifying shell scripts, encoding issues can cause problems.

If you are using Notepad++ on Windows, in the lower-right hand corner, you will see a display that says "Windows (CR LF)". This is perfectly fine for most files. However, if you create and/or modify a shell script in Notepad++, right-click this and change it to "Unix (LF)" before saving the file. Otherwise, it can become corrupted when you transfer it over to the server.

In addition, make sure FileZilla is set to binary transfer mode, not ASCII transfer mode. We cover how to do this in more detail in the "Initial Configuration" section.

Speech Recognition: IBM Watson

The automatic operators on NPSTN are powered by IBM Watson, which allows 100 free minutes of speech recognition per month. If you have a need for speech-to-text in your dialplan, IBM Watson is a highly reliable service that works quite seamlessly with Asterisk.

To get started with IBM Watson Speech to Text, visit its website.

The following shell script can be used for speech-to-text:


curl -X POST -u "apikey:APIKEY" --header "Content-Type: audio/wav" --data-binary @/var/lib/asterisk/sounds/stt/$1.wav "" >/var/lib/asterisk/sounds/stt/$1.txt
#Extract transcript results from JSON response
TRANSCRIPT=`cat /var/lib/asterisk/sounds/stt/$1.txt | grep transcript | sed 's#^.*"transcript": "##g' | sed 's# "$##g' > /var/lib/asterisk/sounds/stt/stt-$1.txt`
sed -e :a -e N -e `s/\n/ /` ta /var/lib/asterisk/sounds/stt/stt-$1.txt >/var/lib/asterisk/sounds/stt/transcripted-stt-$1.txt

rawtext=`cat /var/lib/asterisk/sounds/stt/transcripted-stt-$1.txt`
echo $rawtext

Save this script as /etc/asterisk/scripts/ Now, run the following from the command line:

chmod 777 /etc/asterisk/scripts/
mkdir /var/lib/asterisk/sounds/stt/

To use the shell script, add the following context to your dialplan:

exten => s,1,Set(stt=${UNIQUEID}.${ARG1})
	same => n,Record(/var/lib/asterisk/sounds/stt/${stt}.wav,3,30,qx)
	same => n,Set(text=${SHELL(sh /etc/asterisk/scripts/ ${stt})})
	same => n,Return(${text})

Now, simply call GoSub(stt,s,1(1)) whenever you need speech-to-text. The speech-to-text result will automatically be returned from the subroutine; you will need to use GOSUB_RETVAL to catch it. If you need speech-to-text more than once per call, your next subroutine call should be GoSub(stt,s,1(2)), and so forth. Otherwise, the otherwise-saved transcript results for speech-to-text conversions will be overwritten.

Evan Doorbell

You can load Evan Doorbell recordings onto your Asterisk system for private or public listening pleasure. If you want to do more than a few, however, it becomes quicker to perform batch operations when working with Evan Doorbell's large collection of recordings. Here is one method you can use to make your life easier.

Special thanks to Anthony Hoppe for the Linux scripts and many of the suggestions used here. The Excel instructions and VBA module were added by Naveen Albert.

    Part A: Downloading Files

  1. First, navigate to the page on Evan Doorbell's site containing the audio files you wish to download.
  2. Copy the entire table; the FLAC column is most important, but be sure to include all rows and all columns. If there are multiple tables, don't worry about omitting the text inbetween each table.
  3. Paste into Excel. If there are multiple URLs in a single cell, don't worry; Excel should automatically put each into its own cell.
  4. In Excel, press ALT+F11
  5. Microsoft Visual Basic for Applications should open. Click "Insert", then click "Module".
  6. Paste the following into the empty text window that appears:
    Public Function GetURL(c As Range) As String
        On Error Resume Next
        GetURL = c.Hyperlinks(1).Address
    End Function
  7. Close the VBA window and return to your table in Excel. Save the file with the .xlsm file extension (since the spreadsheet now contains a Macro). Delete the column containing the MP3 URLs. In the leftmost available column to the right of your table, click in the formula bar for the cell whose row contains the first FLAC URL. Paste the following into it: =GetURL($A1) — make sure you change A1 to the cell address containing the FLAC URL. The $ before the A should be kept, as it prevents Excel from automatically changing the column as you drag the formula down.
  8. Now, drag the formula to the bottom of the table so that all rows in that column are populated with the plain text full URL of the hyperlink in the column containing the FLAC URLs copied and pasted from the Evan Doorbell site.
  9. Now, select the entire column containing the plain text URLs (a down arrow should appear when you do this).
  10. Paste this into a blank text file using Notepad. Save as ed.txt
  11. You now have a text file containing the full hyperlinks for all the Evan Doorbell FLAC (high-quality) recordings on a particular page! All that's left now is to actually download them. Here, we will move from Windows to Linux:

  12. Create a new folder on your Asterisk machine by typing mkdir ed
  13. Transfer the text file you just saved in Windows to the ed directory on your Asterisk machine. You can use an SFTP client, like FileZilla, to do this. Or, if it's easier, transfer the file to your web server and download the file directly to the ed directory on your Asterisk server.
  14. Once the text file is saved on your Asterisk machine in the new folder, run the following commands:

  15. cd /root/ed/
    wget -i ed.txt
    Linux is now going to download all the files whose URLs were in that text file. Depending on how many there were, this could take a very long time. Now might be a good time for a coffee break (or a soda break, if you don't drink coffee).

    Part B: Converting Files

  16. Once the files have finished downloading, delete /root/ed/ed.txt by running rm /root/ed/ed.txt
  17. Create a new shell script on your Asterisk machine in the /root/ directory:
    files=($(ls -1 -p $1 | grep -v /))
    mkdir $path/originals
    for f in "${files[@]}"
            echo "Working on file $f"
            length=$(soxi -d $path/"$f")
            fn=$(basename "$f")
            sox -v 0.68 $path/$f -r 8000 -c 1 --type ul "$path"/"$fn".ulaw
            mv $path/$f $path/originals
            echo "$fn" $length >> $path/durations.txt
    If you do this in Windows using Notepad++, be sure to change the encoding in the lower right to Unix (LF). Change the file permissions in Unix to 777.
  18. Save this as (full path is /root/
  19. If you have HD VoIP sets that support G.722, you may want to use the following script instead:

    mkdir originals
    for f in *.flac
            mv -v "$f" "${f// /_}"
    for f in *.flac
            echo "Working on file $f"
            ffmpeg -i "$f" -filter:a volumedetect -f null /dev/null > output.txt 2>&1
            maxVol=$(grep max_volume output.txt)
            diffVol=$(echo "-14 - ${maxVol:53:4}" | bc)
            fn=$(basename "$f")
            ffmpeg -i "$f" -ac 1 -ar 16000 -acodec g722 -filter:a "volume="$diffVol"dB" "$fn".g722
            mv "$f" originals
    		length=$(soxi -d $path/"$fn".g722)
            echo "$fn" $length >> $path/durations.txt
    Save this as — note that you only need to use this script or the other script. Pick one!
  20. Now, run the following commands, waiting for each script or command to finish before proceeding:
    rm /root/ed/ed.txt
    chmod 777 /root/ (replace _ulaw with _g722 if you're using the second script)
    /root/ /root/ed/ (replace _ulaw with _g722 if you're using the second script)
    mv /root/ed/durations.txt /root/
    rm -rf /root/ed/originals/
    mv /root/ed/ /var/lib/asterisk/sounds/en/custom/playback/ed/production/ — adjust path as needed, but moves these recordings into an Asterisk-ready playback location
    tar -czvf production.tar.gz /var/lib/asterisk/sounds/en/custom/playback/ed/production/ — compresses recordings into an archive for backup purposes (SFTP this to your local PC when done) — adjust path as needed
  21. Note: If you get errors and durations are not sent to the text file, run first to get the durations, then use to cut the size of those .wav files in half by turning them into .ulaw files.

    Part C: Metadata

  22. If you're copying and pasting raw recordings, with multiple parts per segment, it gets messy. You'll need to clean up the metadata, which can get involved. We recommend concatenating the tape number followed by the year in parentheses, and then the description using new line breaks where appropriate, all in one cell. You can use the format CONCATENATE(CELL1,CHAR(10),CELL2,CELL3,CHAR(10),CELL4) to do this. CHAR(10) adds a line break to the concatenation. There's no one size fits all formula for this. You'll need to create a separate column and combine several columns, like this: =CONCATENATE("Tape ",$D2," (",$G2,")",CHAR(10),$H2). Make sure that WRAP TEXT is on for cells where you combine data that includes line breaks.
  23. Add a new column to the right of the column containing the raw URL, titled File Name, and fill in the first cell with =LEFT(TRIM(RIGHT(SUBSTITUTE($D2,"/",REPT(" ",LEN($D2))),LEN($D2))),FIND(".",TRIM(RIGHT(SUBSTITUTE($D2,"/",REPT(" ",LEN($D2))),LEN($D2)))&".")-1) — this column should now be populated with the raw names of the files, excluding the directory path.
  24. Now, leaving at least one blank column to the right of your table, open durations.txt and copy and paste it into your Excel sheet.
  25. Select what you copied and pasted into the sheet and go to Insert → Table. Make sure "This table has headers" is unchecked.
  26. Excel will add a header. Rename the column Name/Length. If you didn't start in Row 1 and the table shifts down one cell, select the entire table, hit CTRL+X to move the table and move the entire table one row up. This table should be aligned exactly with the table to the left.
  27. Now, add a new column to the right of your second table titled Length, and fill in the first cell with =RIGHT($K2,LEN($K2)-FIND(" ",$K2)) — this column should now be populated with just the lengths of each recording.
  28. Now comes the key part. Sort both tables — sort the table on the left by File Name and sort the table on the right by Name/Length.
  29. Both tables should have metadata that is now aligned with each other. Go down the table and make sure the contents of File Name match the beginning of the contents of Name/Length on an exact row by row basis (obviously the latter column contains the durations in the same column as well, just ignore this). If all rows in both tables match, you're good to go. If not, you have duplicate entries somewhere. This could be because some Evan Doorbell pages may have duplicate recordings listed. Delete any duplicates and resort and scan the table again until both tables match.
  30. Now, add a new column in the first table titled Duration. Copy the contents of the Length column in the second table. Right-click in the first row under the Duration heading and choose the second option, which should have a "123" icon — this is paste by Values. This copies the durations of each file as text, rather than formulae.
  31. That's it! You now have all the metadata, including the pure filename and length of each recording, in one Excel table! At this point, you can discard the table on the right (the second, smaller table), though it really doesn't hurt to keep it around.

    Part D: Dialplan

  32. Somewhere in your dialplan, perhaps in a separate included file, add the following:
    exten => _[0-9A-Za-z].,1,ControlPlayback(custom/playback/ed/${EXTEN},60000,9,7,#,8,0)
    	same => n,Hangup()
  33. The above dialplan syntax assumes the last 4 digits are sent to an exchange context. If so, in that context, simply include => evandoorbell. If not, you can always add the following in that exchange instead: exten => _X!,1,GoTo(evandoorbell,${EXTEN:-4},1)
  34. Insert a column as the leftmost column in your leftmost Excel table titled #. This will contain the dialable extension number of your recordings. Go through all the recordings you downloaded and assign logical extension numbers. This should be done in chunks, i.e. recordings belong to a series should have consecutive ascending extensions. This will require some thoughtful planning and time.
  35. Add a new column in your leftmost Excel table, titled Dialplan. In this column, we will dynamically generate the dialplan code needed for all these recordings. You do have the filenames in a separate column now, so it would be far easier now than otherwise to manually copy and paste these into new extensions, but that's still a lot of work, don't you think?
  36. In the first table cell in the Dialplan column, populate the cell with =CONCATENATE("exten => ",$A2,",1,GoTo(edrecording,production/",$E2,",1)"). A2 references the column containing your extension numbers and E2 references the column containing your raw file name. Change these if that is not the case. Otherwise, the entire column should be populated with dialplan code!
  37. Now, sort the table by the # column so your extensions are in ascending order.
  38. Copy and paste the cells in the Dialplan column into your Asterisk dialplan file.
  39. Save your dialplan file, and SFTP it over to your Asterisk server if necessary. Reload your dialplan and you are all set!

Now comes the hard part: getting these directory entries into a directory. If you have your own personal webpage with directory extensions, or need to put them into an online DB-based directory, you might need to manually do this extension by extension. However, support for CSV-based upload of directory entries is coming soon to NPSTN, so stay tuned for that! With that functionality, all you would have do is use CONCATENATE to concatenate the prefix (NNX) to the extension number in a separate column and then upload that column along with the Name column (not the File Name column, but rather the title of the recording). Follow the CSV mass-upload instructions if necessary. Note that all Evan Doorbell recordings currently are or will be available on NPSTN's PIoneer7 (747) exchange.

Appendix A: Helpful Supplemental Tools

Using SoX to convert audio files

Although Asterisk can natively play WAV files and MP3 files, these tax the system more heavily than do files designed specifically for use with Asterisk. μLaw (pronounced mu-law, or, frequently but erroneously, u-law), is the codec used in the PSTN (high-quality) and it is what we recommend using for audio files as well. The codec used for mobiles, gsm, is more compressed but poorer quality. Space will not be an issue: WAV files that are 50MB+ can be compressed to just a few MB so we recommend using μLaw files unless you have a good reason to use a different audio codec.

You will need to use the sox utility to convert audio from WAV files to μLaw files. The utility is available for both Linux and Windows so you can either convert the files directly on the server or do them on your local PC and then upload them to the server. If you are working with particularly large WAV files, it is recommended you use the latter approach, as otherwise you will have to transfer extremely large WAV files to the server, only to highly compress them and discard the large WAV files.


The general format for converting WAV files is as follows (from the regular shell, not the Asterisk CLI):

sox input.wav --rate 8000 --channels 1 --type ul output.ulaw lowpass 3400 highpass 300

This optimizes your audio specifically for use with telephone systems. Although much information is lost, the end-result generally still sounds quite good.

You will need to adjust input and output respectively to the full path to your audio files. If you use an FTP client like FileZilla to transfer the audio file cbcad.wav to /var/lib/asterisk/sounds/en/custom/, you will need to type the following instead:

sox /var/lib/asterisk/sounds/en/custom/cbcad.wav --rate 8000 --channels 1 --type ul /var/lib/asterisk/sounds/en/custom/cbcad.ulaw lowpass 3400 highpass 300

As you might imagine, this can become quite tedious if you are using this for a large number of audio files. You can run the following script from the command-line to convert all WAV files in a folder to μLaw files:

for f in *.wav; do
sox $f --rate 8000 --channels 1 --type ul $f.ulaw lowpass 3400 highpass 300

The script above must be run in the same directory as the WAV files in question. Navigate to the directory first, e.g. cd /var/lib/asterisk/sounds/en/custom/wav/

The only problem now is all files will end up with the file extension .wav.ulaw, which is not what you want. You can rename the output file using the MV command:

mv cbcad.wav.ulaw cbcad.ulaw

You can integrate this into your script in such a way that the file extensions are truncated first before the sox command is run, so that way files do not manually need to be renamed afterward, but that is beyond the scope of this documentation.


  1. To use SoX on Windows, download the latest release of SoX for Windows. In this tutorial, we will go with the ZIP file option as opposed to the EXE download.
  2. Next, extract the folder to a directory of your choosing, say, the Documents folder at C:\Users\%username%\Documents\.
  3. Now, rename the folder in which the sox files are contained (probably sox-14.4.2-win32 to, simply, sox.

That's it! Now, as on Linux, you can use sox from the command line by using the command line. To do this, open Command Prompt. Then type cd Documents\sox\ (assuming by default you were in your user profile directory). Now type sox /?, and you should see a list of options. You can try using the following command to manually convert files:

C:\Users\%username%\Documents\sox\sox input.wav --rate 8000 --channels 1 --type ul output.ulaw lowpass 3400 highpass 300

Of course, it is much easier to do a batch convert of files. By creating a simple batch script, you can avoid having to use the command line at all to use SoX!

First, created a folder called bin in your sox directory. Its full path should be C:\Users\%username%\Documents\sox\bin\.

Now, open a text editor and copy and paste the following into it:

@echo off
cd C:\Users\%username%\Documents\sox\bin\
forfiles /S /M *.wav /C "cmd /c rename @file @fname"
for /f "delims=|" %%f in ('dir /b "C:\Users\%username%\Documents\sox\bin\"') do "C:\Users\%username%\Documents\sox\sox.exe" "C:\Users\%username%\Documents\sox\bin\%%f" --rate 8000 --channels 1 --type ul "C:\Users\%username%\Documents\sox\bin\%%f.ulaw" lowpass 3400 highpass 300

You will need to adjust the paths accordingly for your system.

Save the file with a .bat extension, e.g. sox.bat in a convenient location (the sox folder would make sense).

Now, to use your script, simply copy the WAV files you need converted to C:\Users\%username%\Documents\sox\bin\. Then, double-click sox.bat to run the script. The script will automatically truncate the file extensions of all the wav files and then convert all of them to μLaw files. Unlike with other cases in which the original WAV file was left intact, your WAV files will still be left intact but the file extensions will be missing. If you wish to keep the WAV files, you should copy, rather than move the WAV files into the bin directory before running your script. Then, you can simply delete the extension-less files afterward and copy the μLaw files over to your Asterisk server.

Using iptables manually

You can manually create iptables rules with or without fail2ban for granular control over your firewall. We recommend configuring and using fail2ban if possible, but you may wish to add a few manual rules from time to time.

To view your iptables rules at any time, enter the following:

iptables -L -n -v

To view how many times each rule has been used (to get an idea of which IP addresses are spamming you the most), enter:

iptables -nvL --line-numbers

of course, the above will only work if you already have a rule blocking an IP address.

There are a few different types of rules you can use. This one blocks a particular port:

iptables -A INPUT -p udp --destination-port PORT -j DROP

For example, to block port 5060 (if you changed your SIP bindport), replace PORT with 5060.

To block a specific IP address, use the following:

iptables -A INPUT -s IP -j DROP

if you change your mind, you can easily delete the rule:

iptables -D INPUT -s IP -j DROP

You can also block entire ranges of IP addresses by specifying the subnet. By default, iptables -A INPUT -s -j DROP is the same as iptables -A INPUT -s -j DROP.

Hence, to block all of 192.168.1.*, you would enter:

iptables -A INPUT -s -j DROP

To block all of 192.168.*.*, you would enter:

iptables -A INPUT -s -j DROP

To block all of 192.*.*.* (often used for blocking entire countries), use

iptables -A INPUT -s -j DROP

If you notice spammers using multiple similar but different IP addresses, you can use this method to effectively and quickly block entire ranges of IP addresses.

Note that iptables rules are cleared when/if your server is rebooted. To save your iptables rules, you'll need to do the following:

cd /etc/
mkdir iptables

You only need to do the above once. Here's how to save your rules:

sudo iptables-save > /etc/iptables/rules.v4

To restore your rules after a reboot, do the following:

sudo iptables-restore < /etc/iptables/rules.v4

You'll want to run iptables-save after you make any changes to your iptables rules so that they're backed up. You can write a script to automatically restore your iptables rules upon startup if you wish.

If you changed your SIP bindport but are still noticing spammer activity in the Asterisk CLI, you can block their IP addresses as dicated above. Authentication attempts to your server itself (e.g. SSH connections) are logged in /var/log/auth.log. However, don't try to block every malicious IP address you see here — you'll only wear yourself out. Tools like fail2ban are designed to automatically keep on top of spammers and attackers. For further resources on defending your Asterisk system, see some of the VoIP resources on the PhreakNet Resources page.