Getting Started with ARI
Getting Started with ARI
ARI (Asterisk REST Interface) lets external programs control Asterisk over HTTP. Instead of writing all your call logic in the dialplan, you hand a call off to an application that talks to Asterisk via REST calls and receives events over a WebSocket. It is the modern way to build anything more complex than basic call routing.
On this page
When to use ARI (and when not to)
ARI is the right tool when you need to build custom call applications: interactive surveys, call center integrations, click-to-call from a web app, or anything where the logic lives in your own code rather than in extensions.conf.
For straightforward PBX features (ring groups, IVR menus, voicemail, queues), the dialplan is simpler and faster to set up. ARI adds a dependency on an external process and a network connection. If the dialplan can do it, let the dialplan do it.
Enabling the HTTP server
Back up your configuration files
Before editing any Asterisk configuration file, make a backup copy:
bash
sudo cp /etc/asterisk/http.conf /etc/asterisk/http.conf.bak.$(date +%s)
If a typo prevents Asterisk from reloading, you can restore the working version immediately.
ARI runs over Asterisk's built-in HTTP server. Edit /etc/asterisk/http.conf:
[general]
enabled = yes
bindaddr = 127.0.0.1
bindport = 8088
Bind to 127.0.0.1, not 0.0.0.0. The ARI interface has full control over Asterisk, so it should never be exposed to the internet. If your application runs on a different server, put a reverse proxy or SSH tunnel in front of it.
Reload the HTTP module so the http.conf changes take effect:
asterisk -rx "module reload res_http_websocket.so"
asterisk -rx "http show status"
Creating an ARI user
Edit /etc/asterisk/ari.conf:
[general]
enabled = yes
[myapp]
type = user
read_only = no
password = a-strong-random-password
password_format = plain
Reload:
asterisk -rx "module reload res_ari.so"
asterisk -rx "ari show users"
You should see your user listed. If ari show status says "Enabled: No," check http.conf.
The two halves of ARI
ARI has two communication channels:
WebSocket for events. Your application opens a persistent connection to ws://127.0.0.1:8088/ari/events?app=myapp&api_key=myapp:password and receives JSON events whenever something happens to calls your app controls.
REST API for commands. Your application sends HTTP requests to control calls: answer, play audio, bridge channels, hang up.
The pattern is: a call enters your Stasis application, you get a StasisStart event on the WebSocket, then you use REST calls to do things with the channel.
Sending a call to Stasis
In the dialplan, use Stasis() to hand a call to your ARI application:
[from-trunk]
exten => 500,1,Answer()
same => n,Stasis(myapp)
same => n,Hangup()
When someone dials 500, the call enters the myapp Stasis application. Asterisk holds the call and waits for your external application to tell it what to do via REST calls. When your app is done, the call returns to the dialplan and hits Hangup().
Reload the dialplan:
asterisk -rx "dialplan reload"
Testing with curl and wscat
You do not need to write a full application to test ARI. Two terminal windows are enough.
Window 1: WebSocket listener. Install wscat (or use websocat):
npm install -g wscat
wscat -c 'ws://127.0.0.1:8088/ari/events?app=myapp&api_key=myapp:a-strong-random-password'
Window 2: Originate a test call. From the Asterisk CLI:
asterisk -rx 'channel originate Local/500@from-trunk application Wait 30'
In window 1 you should see a StasisStart event with the channel ID. Now use that channel ID to interact with the call.
Window 3: REST commands. Answer the channel and play audio:
# Answer the channel
curl -X POST "http://127.0.0.1:8088/ari/channels/CHANNEL_ID/answer" \
-u myapp:a-strong-random-password
# Play a sound file
curl -X POST "http://127.0.0.1:8088/ari/channels/CHANNEL_ID/play" \
-u myapp:a-strong-random-password \
-H 'Content-Type: application/json' \
-d '{"media": "sound:hello-world"}'
# Hang up
curl -X DELETE "http://127.0.0.1:8088/ari/channels/CHANNEL_ID" \
-u myapp:a-strong-random-password
Replace CHANNEL_ID with the actual ID from the StasisStart event.
A real example: Python
For production use, pick a library instead of raw HTTP calls. Here is a minimal Python example using requests and websocket-client:
import json
import threading
import requests
import websocket
ARI_URL = "http://127.0.0.1:8088/ari"
WS_URL = "ws://127.0.0.1:8088/ari/events?app=myapp&api_key=myapp:a-strong-random-password"
AUTH = ("myapp", "a-strong-random-password")
def on_message(ws, message):
event = json.loads(message)
if event["type"] == "StasisStart":
channel_id = event["channel"]["id"]
print(f"Call started: {channel_id}")
# Answer and play a greeting
requests.post(f"{ARI_URL}/channels/{channel_id}/answer", auth=AUTH)
requests.post(
f"{ARI_URL}/channels/{channel_id}/play",
auth=AUTH,
json={"media": "sound:hello-world"},
)
elif event["type"] == "PlaybackFinished":
# Hang up after the greeting plays
channel_id = event["playback"]["target_uri"].split(":")[-1]
requests.delete(f"{ARI_URL}/channels/{channel_id}", auth=AUTH)
ws = websocket.WebSocketApp(WS_URL, on_message=on_message)
ws.run_forever()
This example is minimal, not production-ready
The code above has no error handling, reconnection logic, or exception handling. A network hiccup or Asterisk restart will crash the script. Production ARI applications need WebSocket reconnection with exponential backoff, HTTP timeout handling, and structured logging.
This answers every incoming call, plays "hello world," and hangs up. Not useful on its own, but it shows the event-driven pattern that every ARI application follows: receive event, make REST calls, receive more events.
Bridging two channels
The most common ARI operation after answering is bridging two channels together (connecting two callers). Create a bridge, then add channels to it:
# Create a mixing bridge
curl -X POST "http://127.0.0.1:8088/ari/bridges" \
-u myapp:a-strong-random-password \
-H 'Content-Type: application/json' \
-d '{"type": "mixing", "name": "my-bridge"}'
# Add two channels to it
curl -X POST "http://127.0.0.1:8088/ari/bridges/BRIDGE_ID/addChannel" \
-u myapp:a-strong-random-password \
-H 'Content-Type: application/json' \
-d '{"channel": "CHANNEL_ID_1,CHANNEL_ID_2"}'
Once both channels are in the bridge, the callers can hear each other. Remove a channel from the bridge to put them on hold, add them to a different bridge for a transfer, or delete the bridge to disconnect everyone.
Security
ARI gives full control over Asterisk. Treat it accordingly:
- Bind the HTTP server to
127.0.0.1only - Use strong passwords, not the defaults from tutorials
- If the ARI client runs on a separate machine, use an SSH tunnel or TLS-enabled reverse proxy
- Create read-only users for monitoring applications that should not be able to modify calls
- Keep Asterisk and its libraries updated; the HTTP server has had vulnerabilities in the past
Common problems
WebSocket connects but no events arrive. The app parameter in the WebSocket URL must match the name in Stasis(myapp) in the dialplan. Case-sensitive.
"404 Not Found" on REST calls. The channel ID has expired (call already hung up) or was never part of your Stasis application. Channels only appear in ARI while they are inside a Stasis() call.
Call enters Stasis and immediately hangs up. Your WebSocket client is not connected, or connected to a different app name. Asterisk has nobody to hand the call to, so it falls through to the next dialplan priority.
Audio does not play. The media parameter must be sound:filename (without the file extension). The sound file needs to exist in /var/lib/asterisk/sounds/ in a format Asterisk can use.
What to read next
- ARI Setup and Stasis Application for a more complete integration example
- Stasis() reference for the dialplan application
- The Asterisk ARI documentation for the full REST API reference
User Notes
Know a tip or gotcha for this topic? Share it below and help others.
Contribute a note
Share a tip, gotcha, or practical example. Keep it under 2000 characters. No questions (use the Asterisk community forums for support). Wrap code in backticks.