Asterisk and FreePBX
Add Tone as a PJSIP trunk on Asterisk or FreePBX, and route selected extensions or DIDs to an agent.
If your numbers already land on an Asterisk or FreePBX box, Tone becomes one more trunk on it. This page has the config, both hand-written PJSIP and the FreePBX fields that generate it.
Before you start
- Asterisk 16 or later with
res_pjsip(thechan_sipchannel driver is deprecated and removed in Asterisk 21 — use PJSIP). - A static public IPv4 address on the Asterisk box, or a NAT whose public address is fixed.
- A Tone organization with KYC verified and SIP trunking enabled by Tone support.
Create the trunk in Tone first
You need the credentials before you can write the Asterisk config. Digest is the easier identity here, because a self-hosted PBX often sits behind a dynamic or shared address:
POST /v1/sip-trunks
{
"name": "Asterisk — office",
"inboundAuth": "digest",
"outboundHost": "pbx.yourcompany.example",
"outboundPort": 5061,
"outboundTransport": "tls",
"outboundNumberFormat": "e164",
"callerIdPolicy": "did",
"codecs": ["PCMA", "PCMU"],
"maxConcurrent": 10
}The response carries connection.username, connection.password and connection.realm
once. Copy them straight into the config below.
If your box does have a fixed address, prefer "inboundAuth": "both" with an ipAcl — it is one
more thing an attacker has to have, and it costs you nothing.
PJSIP configuration
pjsip.conf — replace t_9f2c… and the password with your connection values:
Tone does not require registration, so there is no type = registration section here at all.
If your NAT needs a keepalive, the AOR's qualify_frequency below provides one.
[tone-auth]
type = auth
auth_type = userpass
username = t_9f2c…
password = <from connection.password>
realm = sip.usetone.ai
[tone-aor]
type = aor
contact = sip:sip.usetone.ai:5061\;transport=tls
qualify_frequency = 60
[tone-endpoint]
type = endpoint
context = from-tone
transport = transport-tls
aors = tone-aor
outbound_auth = tone-auth
disallow = all
allow = alaw
allow = ulaw
dtmf_mode = rfc4733
direct_media = no
rtp_symmetric = yes
force_rport = yes
rewrite_contact = yes
from_domain = sip.usetone.ai
[tone-identify]
type = identify
endpoint = tone-endpoint
match = <Tone signalling IP 1>
match = <Tone signalling IP 2>Three lines are load-bearing:
direct_media = no. Asterisk must stay in the media path. If it re-invites the two ends to talk directly, Tone's gateway and your carrier will try to send RTP to each other and neither will be expecting it.dtmf_mode = rfc4733— this is RFC 2833 under its current name.inbandwill not reach the agent as digits, so DTMF-based opt-out stops working.[tone-identify]with Tone's signalling IPs, from your trunk's page in the dashboard. Without it, inbound calls from Tone match no endpoint and land in the anonymous context.
Your transport, if you do not already have a TLS one:
[transport-tls]
type = transport
protocol = tls
bind = 0.0.0.0:5061
cert_file = /etc/asterisk/keys/pbx.crt
priv_key_file = /etc/asterisk/keys/pbx.key
method = tlsv1_2Dialplan
Inbound from Tone — calls the agent placed, arriving at your PBX:
[from-tone]
exten => _X.,1,NoOp(Call from Tone for ${EXTEN})
same => n,Dial(PJSIP/${EXTEN}@your-carrier-trunk,60)
same => n,Hangup()Outbound to Tone — the DIDs you want an agent to answer. Send only those, and leave the rest of your dial plan alone:
[from-carrier]
exten => _+919876543210,1,NoOp(Routing to Tone agent)
same => n,Dial(PJSIP/${EXTEN}@tone-endpoint,60)
same => n,Hangup()Tone answers only the DIDs you imported, and only on the trunk they were imported on — anything
else gets a 404. That makes it safe to experiment with routing: a mistake fails loudly rather
than reaching the wrong agent.
FreePBX
The same thing through the GUI — Connectivity → Trunks → Add SIP (chan_pjsip) Trunk:
| Field | Value |
|---|---|
| Trunk Name | tone |
| Outbound — Username | connection.username |
| Outbound — Secret | connection.password |
| Outbound — SIP Server | sip.usetone.ai |
| Outbound — SIP Server Port | 5061 |
| Outbound — Transport | the TLS transport |
| Outbound — Authentication | Outbound |
| Outbound — Registration | None |
| Codecs | alaw, ulaw only |
| Advanced — Direct Media | No |
| Advanced — DTMF Mode | RFC 4733 |
| Advanced — Match (Permit) | Tone's signalling IPs |
Then an Inbound Route per DID pointing at the trunk, and an Outbound Route for calls the agent places. Set Registration to None — FreePBX defaults to sending a registration, and Tone will not answer one.
Import your numbers
POST /v1/sip-trunks/{id}/numbers
{ "e164": "+919876543210", "agentId": "…" }Testing
asterisk -rx "pjsip show endpoint tone-endpoint"
asterisk -rx "pjsip show aor tone-aor"The AOR should qualify as Avail. Then POST /v1/sip-trunks/{id}/test from Tone's side, call a
DID from a mobile, and finally place a POST /v1/calls.
When it does not work
| What you see | Why |
|---|---|
| Inbound from Tone lands in the wrong context | [tone-identify] missing or the wrong IPs, so the call matched no endpoint. |
401/407 loop on outbound | outbound_auth not set on the endpoint, or realm is not sip.usetone.ai. |
| Ringing then silence | direct_media is still yes — Asterisk stepped out of the media path. |
| DTMF ignored by the agent | dtmf_mode is inband or info. It must be rfc4733. |
Tone returns 403 | Trunk not approved yet, credentials wrong, or — with inboundAuth: "both" — your public address is not in the ipAcl. |
Tone returns 404 | The DID was never imported, or was imported on a different trunk. |
| TLS handshake fails | Asterisk is validating Tone's certificate against the wrong name; the peer is sip.usetone.ai. |