Claiming Broadcast Channels¶
This guide walks through a complete StasisBroadcast setup: Asterisk configuration, the dialplan, the CallBroadcast event your application receives, the claim REST call, and working examples in Python and Node.js.
Prerequisites¶
- Asterisk 20.20, 22.10, or 23.4 or later with
res_stasis_broadcast.soandapp_stasis_broadcast.sobuilt and installed. - The ARI HTTP interface enabled (see below).
- One or more ARI applications connected via WebSocket.
Configuring Asterisk¶
Enable the HTTP Server¶
ARI relies on Asterisk's built-in HTTP server. Enable it in http.conf:
Configure ARI¶
Create at least one ARI user in ari.conf. The same credentials are used for both the REST API and the WebSocket connection:
| ari.conf | |
|---|---|
Channel variables in events
To include channel variable values in CallBroadcast events (for routing decisions in your ARI application), list the variable names in ari.conf:
Variables will appear inside the channel.channelvars object of each event.
Load the Modules¶
Ensure both modules are listed (or autoloaded) in modules.conf:
| modules.conf | |
|---|---|
Dialplan Configuration¶
Use StasisBroadcast() anywhere in the dialplan in place of Stasis(). After the application returns, branch on the STASISSTATUS channel variable to handle the outcome. A complete set of patterns with inline comments is provided in the examples below.
Application Parameters¶
StasisBroadcast([timeout[,app_filter[,args[,notify_claimed]]]])
Arguments are comma-delimited. All are optional.
| Parameter | Type | Default | Description |
|---|---|---|---|
timeout | integer (ms) | 500 | Milliseconds to wait for a claim before returning to the dialplan. Valid range: 0–60000. |
app_filter | regex | (all apps) | Regular expression applied to ARI application names. Only matching applications receive the CallBroadcast event. Because arguments are comma-delimited, commas cannot appear in the pattern — use character classes such as [,] if needed. |
args | string | (none) | Colon-delimited arguments passed to the winning application in its StasisStart event as the args array. The colon separator is used because commas separate StasisBroadcast() parameters. Equivalent to Stasis(app,arg1,arg2) — the winner receives the same args array. Example: sales:priority-high. |
notify_claimed | boolean | no | When yes, a CallClaimed event is sent to all filtered applications once the channel is claimed. Disabled by default to minimise WebSocket traffic; losing claimants already receive a 409 HTTP response. To set only this parameter while accepting defaults for the others, use empty commas as placeholders: StasisBroadcast(,,, yes). |
Examples¶
The following annotated sample covers the most common patterns:
;
; Example Asterisk dialplan configuration for StasisBroadcast()
;
; StasisBroadcast() broadcasts a channel to all connected ARI applications
; (or a filtered subset). The first application to claim the channel wins,
; and the channel is automatically placed under Stasis control with that
; application -- exactly as if Stasis(winner_app) had been called.
;
; If no application claims within the timeout, STASISSTATUS is set to
; "TIMEOUT" and control returns to the dialplan for fallback handling.
;
; Channel variables set by StasisBroadcast():
; STASISSTATUS = SUCCESS | FAILED | TIMEOUT
;
; Syntax (all arguments are positional and optional):
; StasisBroadcast([timeout[,app_filter[,args[,notify_claimed]]]])
;
; timeout Claim timeout in ms (default: 500, max: 60000)
; app_filter Regex: only broadcast to matching app names
; args Colon-delimited args passed to the winner via StasisStart
; notify_claimed Send CallClaimed event to filtered apps (default: no)
;
[general]
; Basic broadcast -- all ARI apps, default 500 ms timeout
[broadcast-basic]
exten => _X.,1,NoOp(Incoming call from ${CALLERID(num)} to ${EXTEN})
same => n,StasisBroadcast()
same => n,GotoIf($["${STASISSTATUS}"="TIMEOUT"]?no_route)
same => n,Hangup()
same => n(no_route),Playback(sorry-no-agent-available)
same => n,Hangup()
; Custom timeout (2 s) and application filter
[broadcast-filtered]
exten => _X.,1,NoOp(Sales call routing)
same => n,StasisBroadcast(2000,^sales_.*)
same => n,GotoIf($["${STASISSTATUS}"="TIMEOUT"]?no_route)
same => n,Hangup()
same => n(no_route),Playback(sorry-sales-closed)
same => n,Hangup()
; Arguments passed to the winning application
; Args appear in the StasisStart event, just like Stasis(app,arg1:arg2)
[broadcast-with-args]
exten => _X.,1,NoOp(Support call with context args)
same => n,StasisBroadcast(2000,^support_.*,tier1:english)
same => n,GotoIf($["${STASISSTATUS}"="TIMEOUT"]?no_route)
same => n,Hangup()
same => n(no_route),Playback(sorry-support-unavailable)
same => n,Hangup()
; Skill-based routing using channel variables
; ARI apps receive channel variables in the CallBroadcast event
; (requires the variable names to be listed in ari.conf "channelvars")
; and can decide whether to claim based on them
[broadcast-skillbased]
exten => _X.,1,NoOp(Skill-based routing)
same => n,Set(CHANNEL(language)=en)
same => n,Set(SKILL_REQUIRED=advanced)
same => n,Set(PRIORITY=high)
same => n,StasisBroadcast(3000,^agent_.*)
same => n,GotoIf($["${STASISSTATUS}"="TIMEOUT"]?no_route)
same => n,Hangup()
same => n(no_route),NoOp(No qualified agent available)
same => n,Playback(please-hold)
same => n,Queue(default-queue)
same => n,Hangup()
; Broadcast with CallClaimed notifications enabled for observability
; notify_claimed=yes sends a CallClaimed event to all filtered apps
; when a channel is claimed (disabled by default to reduce traffic)
[broadcast-with-notify]
exten => _X.,1,NoOp(Broadcast with claim notifications)
same => n,StasisBroadcast(500,^agent_.*,,yes)
same => n,GotoIf($["${STASISSTATUS}"="TIMEOUT"]?no_route)
same => n,Hangup()
same => n(no_route),Playback(sorry-no-agent-available)
same => n,Hangup()
; Fallback to traditional queue if no ARI app claims
[broadcast-with-fallback]
exten => _X.,1,NoOp(Broadcast with queue fallback)
same => n,StasisBroadcast(500,^premium_.*)
same => n,GotoIf($["${STASISSTATUS}"="TIMEOUT"]?fallback)
same => n,Hangup()
same => n(fallback),NoOp(No premium agent, falling back to queue)
same => n,Queue(default-queue,t,,,60)
same => n,Playback(goodbye)
same => n,Hangup()
STASISSTATUS Variable¶
After StasisBroadcast() returns, the STASISSTATUS channel variable contains the outcome:
| Value | Meaning |
|---|---|
SUCCESS | An application claimed the channel and the Stasis session completed without error. |
FAILED | An application claimed the channel but an error occurred when executing the Stasis application. |
TIMEOUT | No application claimed the channel within the timeout. |
ARI Events¶
CallBroadcast Event¶
Sent simultaneously over the WebSocket to all connected ARI applications (or the filtered subset) when StasisBroadcast() is called. Your application evaluates the event and decides whether to claim the channel.
{
"type": "CallBroadcast",
"application": "my-ari-app",
"timestamp": "2026-02-25T10:15:00.000+0000",
"asterisk_id": "my-asterisk-server",
"channel": {
"id": "1740477300.1",
"name": "PJSIP/Alice-00000001",
"state": "Up",
"caller": {
"name": "Alice",
"number": "200"
},
"connected": {
"name": "",
"number": ""
},
"accountcode": "",
"dialplan": {
"context": "default",
"exten": "1000",
"priority": 3
},
"creationtime": "2026-02-25T10:15:00.000+0000",
"language": "en",
"channelvars": {
"SKILL_REQUIRED": "billing",
"PRIORITY": "high"
}
},
"caller": "200",
"called": "1000"
}
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | Always "CallBroadcast". |
application | string | yes | Name of the ARI application receiving this event. |
timestamp | Date | yes | Time the broadcast was initiated. |
channel | Channel | yes | Full channel snapshot. Includes channelvars if configured in ari.conf. |
caller | string | no | Caller ID number of the originating party. |
called | string | no | Dialled extension. |
asterisk_id | string | no | Asterisk system identifier. |
CallClaimed Event¶
Sent when a channel has been successfully claimed, if notify_claimed was set to yes in the dialplan. Useful for dashboards or observability tooling; not required for normal claim-based dispatch.
{
"type": "CallClaimed",
"application": "my-ari-app",
"timestamp": "2026-02-25T10:15:00.123+0000",
"asterisk_id": "my-asterisk-server",
"channel": { "...": "..." },
"winner_app": "billing_agent_1"
}
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | Always "CallClaimed". |
application | string | yes | Name of the ARI application receiving this event. |
timestamp | Date | yes | Time the claim was accepted. |
channel | Channel | yes | Channel snapshot at claim time. |
winner_app | string | yes | Name of the application that won the claim. |
Claiming a Channel¶
To claim a channel, issue an HTTP POST to /ari/events/claim:
| Parameter | Required | Description |
|---|---|---|
channelId | yes | The unique ID of the channel from the CallBroadcast event (channel.id). |
application | yes | The ARI application name making the claim. Must match the application's registered name. |
Response Codes¶
| Code | Meaning |
|---|---|
204 No Content | Claim accepted. The channel will enter your application via a StasisStart event. |
409 Conflict | Another application already claimed this channel. |
404 Not Found | No broadcast is active for the given channel ID (already timed out or cleaned up). |
Race timing
The claim endpoint is designed for concurrent access. Multiple applications can call it simultaneously; exactly one will receive 204. The rest receive 409. There is no need for external locking or coordination between applications.
Example: Python¶
The following is a complete, self-contained ARI client that connects to Asterisk, listens for CallBroadcast events, applies routing logic, and claims matching calls. When the channel arrives via StasisStart, it answers and plays a greeting.
| broadcast_agent.py | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
Start multiple instances, each with a distinct application name, to simulate competing agents:
python3 broadcast_agent.py billing_agent_1 localhost:8088 asterisk asterisk &
python3 broadcast_agent.py billing_agent_2 localhost:8088 asterisk asterisk &
python3 broadcast_agent.py support_agent_1 localhost:8088 asterisk asterisk &
Example: Node.js¶
The Node.js example uses the ari-client library and follows the same structure as the Python example.
Advanced Topics¶
Filtering by Application Name¶
The app_filter parameter accepts a regular expression. Only ARI applications whose registered name matches the regex receive the CallBroadcast event:
; Only notify applications whose names start with "billing_"
exten => 3000,1,StasisBroadcast(1000,^billing_)
; Notify apps matching either "sales" or "support"
exten => 4000,1,StasisBroadcast(1000,sales|support)
; Use a channel variable to build the regex dynamically
; (dialplan variables are expanded before the application sees its arguments)
exten => 5000,1,Set(REGION=eu-west)
same => n,StasisBroadcast(2000,^agent-${REGION}-.*)
Note
Because dialplan arguments are comma-delimited, literal commas are not allowed in the regex. Use character classes ([,]) if a literal comma is required. In practice, application names do not contain commas, so this is rarely a concern.
Routing with Channel Variables¶
For routing decisions based on call metadata, set channel variables before calling StasisBroadcast() and list them in ari.conf under channelvars:
exten => _1XXX,1,NoOp()
same => n,Set(SKILL_REQUIRED=billing)
same => n,Set(PRIORITY=high)
same => n,StasisBroadcast(1000)
The variables appear in event.channel.channelvars in the CallBroadcast event, allowing each ARI application to make an informed routing decision without a centralised lookup.
Timeout and Fallback Handling¶
Choose a timeout that balances responsiveness against the time your applications need to evaluate the call:
[default]
exten => _X.,1,Answer()
same => n,StasisBroadcast(2000) ; wait up to 2 seconds
; --- Handle all outcomes ---
same => n,GotoIf($["${STASISSTATUS}" = "SUCCESS"]?done)
same => n,GotoIf($["${STASISSTATUS}" = "FAILED"]?failed)
; TIMEOUT: no agent claimed the call
same => n,Playback(sorry-no-agent)
same => n,Hangup()
same => n(done),Hangup()
same => n(failed),Playback(an-error-has-occured)
same => n,Hangup()
Passing Arguments to the Winning Application¶
The args parameter is forwarded to the winning application in its StasisStart event as the args array, equivalent to Stasis(app,arg1,arg2).
Colon delimiter vs. Stasis() comma delimiter
In Stasis(app,arg1,arg2), extra arguments are additional comma-delimited positions in the dialplan. In StasisBroadcast(), the entire args value is a single comma-delimited position, so a different separator is needed inside it: colons. Both end up producing the same args array in the StasisStart event.
In the winning application: