# Asterisk and FreePBX

Source: https://docs.usetone.ai/sip-trunking/asterisk-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` (the `chan_sip` channel 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:

```http
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.

```ini
[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. `inband` will 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:

```ini
[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_2
```

## Dialplan

Inbound from Tone — calls the agent placed, arriving at your PBX:

```ini
[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:

```ini
[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

```http
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`. |
