Getting Started with ARI

Getting Started -- Last reviewed 2026-06-13 ari stasis rest-api python websocket Found this useful? Upvote it. ×

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:

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.

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.

Moderated before publishing. Email never shown.
Related Snippets