gamlin commited on
Commit
d15eabd
Β·
verified Β·
0 Parent(s):

initial commit

Browse files
Files changed (2) hide show
  1. .gitattributes +35 -0
  2. README.md +749 -0
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,749 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ tags:
4
+ - vicidial
5
+ - call-center
6
+ - asterisk
7
+ - ami
8
+ - commands
9
+ ---
10
+
11
+ # Asterisk AMI Commands Guide
12
+
13
+ **Last updated: March 2026 | Reading time: ~26 minutes** The [Asterisk Manager Interface](/blog/asterisk-manager-interface-guide/) (AMI) is the programmatic backdoor to your PBX. It's a TCP socket protocol that lets external programs control Asterisk β€” originate calls, redirect channels, read variables, monitor events, and do anything the Asterisk CLI can do, but from code. VICIdial uses AMI extensively under the hood. The dialer cron scripts originate calls through AMI. The agent screen sends DTMF through AMI. Call monitoring, conference management, and agent session control all go through AMI. If you understand AMI, you understand the engine that drives VICIdial's real-time operations. The official documentation lists 150+ commands. You'll use maybe 15 of them regularly. Here are those 15, with the exact syntax and practical examples. --- AMI is configured in `/etc/asterisk/manager.conf`. VICIbox ships with this pre-configured for VICIdial, but understanding the config helps when you need to add custom integrations. ```ini [general]...
14
+
15
+ ## Overview
16
+
17
+ **Last updated: March 2026 | Reading time: ~26 minutes**
18
+
19
+ The [Asterisk Manager Interface](/blog/asterisk-manager-interface-guide/) (AMI) is the programmatic backdoor to your PBX. It's a TCP socket protocol that lets external programs control Asterisk β€” originate calls, redirect channels, read variables, monitor events, and do anything the Asterisk CLI can do, but from code.
20
+
21
+ VICIdial uses AMI extensively under the hood. The dialer cron scripts originate calls through AMI. The agent screen sends DTMF through AMI. Call monitoring, conference management, and agent session control all go through AMI. If you understand AMI, you understand the engine that drives VICIdial's real-time operations.
22
+
23
+ The official documentation lists 150+ commands. You'll use maybe 15 of them regularly. Here are those 15, with the exact syntax and practical examples.
24
+
25
+ ---
26
+
27
+ ## Setting Up AMI
28
+
29
+ ### manager.conf
30
+
31
+ AMI is configured in `/etc/asterisk/manager.conf`. VICIbox ships with this pre-configured for VICIdial, but understanding the config helps when you need to add custom integrations.
32
+
33
+ ```ini
34
+ [general]
35
+ enabled = yes
36
+ port = 5038
37
+ bindaddr = 127.0.0.1
38
+ displayconnects = yes
39
+ timestampevents = yes
40
+
41
+ [cron]
42
+ secret = YOUR_SECRET_HERE
43
+ read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan,originate
44
+ write = system,call,agent,user,config,command,reporting,originate
45
+
46
+ [monitoring]
47
+ secret = ANOTHER_SECRET
48
+ read = system,call,agent,reporting,cdr
49
+ write = command
50
+ ```
51
+
52
+ Key configuration points:
53
+
54
+ **`bindaddr = 127.0.0.1`**: Binds AMI to localhost only. This means only programs running on the same server can connect. This is the safe default. If you need remote AMI access (for external dashboards or integrations), change this to `0.0.0.0` and add firewall rules.
55
+
56
+ **`read` permissions**: What events and data this user can receive.
57
+ **`write` permissions**: What actions this user can execute.
58
+
59
+ The permission categories:
60
+ | Permission | What It Controls |
61
+ |-----------|-----------------|
62
+ | `system` | System status, module management, shutdown |
63
+ | `call` | Channel events, call origination |
64
+ | `log` | Log events |
65
+ | `verbose` | Verbose messages |
66
+ | `agent` | Agent-related events and actions |
67
+ | `user` | User events |
68
+ | `config` | Configuration changes |
69
+ | `dtmf` | DTMF events and sending |
70
+ | `reporting` | CDR and reporting events |
71
+ | `cdr` | CDR data |
72
+ | `dialplan` | Dialplan events |
73
+ | `originate` | Call origination |
74
+ | `command` | CLI command execution through AMI |
75
+
76
+ After editing, reload:
77
+
78
+ ```bash
79
+ asterisk -rx "manager reload"
80
+ ```
81
+
82
+ ### Testing Connectivity
83
+
84
+ The quickest way to test AMI:
85
+
86
+ ```bash
87
+ telnet 127.0.0.1 5038
88
+ ```
89
+
90
+ You'll see:
91
+
92
+ ```
93
+ Asterisk Call Manager/6.0.0
94
+ ```
95
+
96
+ Authenticate:
97
+
98
+ ```
99
+ Action: Login
100
+ Username: cron
101
+ Secret: YOUR_SECRET_HERE
102
+
103
+ ```
104
+
105
+ (Note the blank line after Secret β€” that's required. AMI uses blank lines as packet delimiters.)
106
+
107
+ Response:
108
+
109
+ ```
110
+ Response: Success
111
+ Message: Authentication accepted
112
+ ```
113
+
114
+ You're in. Now you can type actions directly.
115
+
116
+ ---
117
+
118
+ ## The Core AMI Actions
119
+
120
+ ### 1. Originate β€” Make a Call
121
+
122
+ The most powerful AMI action. Tells Asterisk to originate a new call to a destination.
123
+
124
+ **Two modes:**
125
+
126
+ **Application mode** β€” Call a number and run a dialplan application:
127
+
128
+ ```
129
+ Action: Originate
130
+ Channel: PJSIP/carrier/sip:3125551234@carrier.com
131
+ Context: from-internal
132
+ Exten: s
133
+ Priority: 1
134
+ CallerID: "Sales" <8005551234>
135
+ Timeout: 30000
136
+ Variable: campaign_id=SALES01
137
+ ActionID: orig-001
138
+
139
+ ```
140
+
141
+ **Async mode** β€” Same but returns immediately instead of waiting for the call to connect:
142
+
143
+ ```
144
+ Action: Originate
145
+ Channel: PJSIP/carrier/sip:3125551234@carrier.com
146
+ Application: MeetMe
147
+ Data: 8600100|qF
148
+ CallerID: "Sales" <8005551234>
149
+ Timeout: 30000
150
+ Async: true
151
+ ActionID: orig-002
152
+
153
+ ```
154
+
155
+ VICIdial uses Application mode with `MeetMe` to bridge calls into conference rooms where agents are already waiting. That's how the predictive dialer works β€” it originates a call, and when it connects, the call lands in a MeetMe room where an agent is listening.
156
+
157
+ **Important parameters:**
158
+ - `Channel`: The outbound SIP channel (format depends on chan_sip vs PJSIP)
159
+ - `Context`/`Exten`/`Priority`: Where to send the call in the dialplan after it connects
160
+ - `Application`/`Data`: Alternative to Context β€” run a specific application directly
161
+ - `CallerID`: The caller ID to present
162
+ - `Timeout`: Ring timeout in milliseconds (30000 = 30 seconds)
163
+ - `Async`: Return immediately without waiting for call outcome
164
+ - `Variable`: Set channel variables (key=value, comma-separated for multiple)
165
+ - `ActionID`: Your identifier for matching the response
166
+
167
+ ### 2. Redirect β€” Transfer a Call
168
+
169
+ Move an active channel to a different dialplan destination:
170
+
171
+ ```
172
+ Action: Redirect
173
+ Channel: PJSIP/carrier-00000042
174
+ Context: transfer-dest
175
+ Exten: 3125559876
176
+ Priority: 1
177
+ ActionID: redir-001
178
+
179
+ ```
180
+
181
+ For attended transfers (redirect two channels simultaneously):
182
+
183
+ ```
184
+ Action: Redirect
185
+ Channel: PJSIP/carrier-00000042
186
+ ExtraChannel: PJSIP/agent-00000043
187
+ Context: transfer-dest
188
+ Exten: 3125559876
189
+ Priority: 1
190
+ ExtraContext: from-internal
191
+ ExtraExten: hangup
192
+ ExtraPriority: 1
193
+ ActionID: redir-002
194
+
195
+ ```
196
+
197
+ VICIdial uses Redirect for blind transfers, warm transfers, and parking. When an agent clicks "Transfer" on the agent screen, the web interface sends an AMI Redirect action to move the call.
198
+
199
+ ### 3. Hangup β€” End a Call
200
+
201
+ Terminate a specific channel:
202
+
203
+ ```
204
+ Action: Hangup
205
+ Channel: PJSIP/carrier-00000042
206
+ Cause: 16
207
+ ActionID: hangup-001
208
+
209
+ ```
210
+
211
+ Cause code 16 is "Normal clearing" β€” the standard hangup cause. Other common codes:
212
+ - 17 = User busy
213
+ - 19 = No answer
214
+ - 21 = Call rejected
215
+ - 31 = Normal, unspecified
216
+
217
+ ### 4. Status β€” Query Active Channels
218
+
219
+ Get information about active channels:
220
+
221
+ ```
222
+ Action: Status
223
+ ActionID: status-001
224
+
225
+ ```
226
+
227
+ Returns a list of all active channels with their state, caller ID, duration, and bridged channel. For a specific channel:
228
+
229
+ ```
230
+ Action: Status
231
+ Channel: PJSIP/carrier-00000042
232
+ ActionID: status-002
233
+
234
+ ```
235
+
236
+ This is how monitoring systems check how many calls are active on the system at any moment.
237
+
238
+ ### 5. CoreShowChannels β€” List All Channels
239
+
240
+ Similar to Status but returns a more detailed listing:
241
+
242
+ ```
243
+ Action: CoreShowChannels
244
+ ActionID: channels-001
245
+
246
+ ```
247
+
248
+ Response includes channel name, context, extension, priority, state, application, duration, caller ID, and account code for every active channel. Useful for building real-time call dashboards.
249
+
250
+ ### 6. GetVar β€” Read a Channel Variable
251
+
252
+ Read the value of a channel variable or dialplan function:
253
+
254
+ ```
255
+ Action: GetVar
256
+ Channel: PJSIP/carrier-00000042
257
+ Variable: CALLERID(num)
258
+ ActionID: getvar-001
259
+
260
+ ```
261
+
262
+ Response:
263
+
264
+ ```
265
+ Response: Success
266
+ Variable: CALLERID(num)
267
+ Value: 3125551234
268
+ ActionID: getvar-001
269
+ ```
270
+
271
+ You can read any channel variable or dialplan function this way. Useful for checking call state, reading custom data attached to calls, and debugging dialplan logic.
272
+
273
+ ### 7. SetVar β€” Write a Channel Variable
274
+
275
+ Set a variable on an active channel:
276
+
277
+ ```
278
+ Action: SetVar
279
+ Channel: PJSIP/carrier-00000042
280
+ Variable: CUSTOM_DATA
281
+ Value: priority_customer
282
+ ActionID: setvar-001
283
+
284
+ ```
285
+
286
+ You can also set global variables (no Channel parameter):
287
+
288
+ ```
289
+ Action: SetVar
290
+ Variable: GLOBAL_SETTING
291
+ Value: enabled
292
+ ActionID: setvar-002
293
+
294
+ ```
295
+
296
+ ### 8. Command β€” Execute CLI Commands
297
+
298
+ Run any Asterisk CLI command through AMI:
299
+
300
+ ```
301
+ Action: Command
302
+ Command: core show channels
303
+ ActionID: cmd-001
304
+
305
+ ```
306
+
307
+ Response includes the full CLI output. This is the escape hatch β€” anything you can type in `asterisk -r` you can execute through AMI.
308
+
309
+ Common commands:
310
+ - `sip show peers` / `pjsip show endpoints` β€” Check SIP registrations
311
+ - `core show channels` β€” Active channel count
312
+ - `queue show` β€” Queue statistics
313
+ - `database show` β€” AstDB contents
314
+ - `module reload res_pjsip.so` β€” Reload PJSIP without restarting
315
+
316
+ ### 9. QueueStatus β€” Get Queue Information
317
+
318
+ Query the current state of Asterisk queues:
319
+
320
+ ```
321
+ Action: QueueStatus
322
+ Queue: support-queue
323
+ ActionID: queue-001
324
+
325
+ ```
326
+
327
+ Returns queue members, their status (available/paused/busy), and current callers waiting. For VICIdial, queue data is managed differently (through the VICIdial application layer), but this is useful for hybrid setups that use both VICIdial and native Asterisk queues.
328
+
329
+ ### 10. Monitor / StopMonitor β€” Call Recording
330
+
331
+ Start recording an active channel:
332
+
333
+ ```
334
+ Action: Monitor
335
+ Channel: PJSIP/carrier-00000042
336
+ File: /var/spool/asterisk/monitor/20260326-call-042
337
+ Format: wav
338
+ Mix: true
339
+ ActionID: monitor-001
340
+
341
+ ```
342
+
343
+ Stop recording:
344
+
345
+ ```
346
+ Action: StopMonitor
347
+ Channel: PJSIP/carrier-00000042
348
+ ActionID: stopmon-001
349
+
350
+ ```
351
+
352
+ VICIdial handles recording through its own mechanism (campaign-level recording settings), but Monitor/StopMonitor is useful for on-demand recording of specific calls from external systems.
353
+
354
+ ---
355
+
356
+ ## AMI Events: Real-Time Call Tracking
357
+
358
+ AMI isn't just for sending commands β€” it's also a real-time event stream. When you connect and authenticate, Asterisk pushes events to your connection as they happen.
359
+
360
+ ### Key Events
361
+
362
+ **Newchannel** β€” A new channel was created (call initiated):
363
+
364
+ ```
365
+ Event: Newchannel
366
+ Channel: PJSIP/carrier-00000042
367
+ ChannelState: 0
368
+ ChannelStateDesc: Down
369
+ CallerIDNum: 3125551234
370
+ CallerIDName: John Smith
371
+ Uniqueid: 1711432800.42
372
+ ```
373
+
374
+ **Hangup** β€” A channel was hung up:
375
+
376
+ ```
377
+ Event: Hangup
378
+ Channel: PJSIP/carrier-00000042
379
+ Cause: 16
380
+ Cause-txt: Normal Clearing
381
+ Uniqueid: 1711432800.42
382
+ ```
383
+
384
+ **BridgeEnter** / **BridgeLeave** β€” Channels joining/leaving bridges:
385
+
386
+ ```
387
+ Event: BridgeEnter
388
+ Channel: PJSIP/carrier-00000042
389
+ BridgeUniqueid: bridge-001
390
+ BridgeType: basic
391
+ ```
392
+
393
+ **AgentConnect** β€” An agent connected to a queue call:
394
+
395
+ ```
396
+ Event: AgentConnect
397
+ Queue: sales-queue
398
+ Interface: PJSIP/agent001
399
+ MemberName: Agent Smith
400
+ HoldTime: 12
401
+ ```
402
+
403
+ **DTMFReceived** β€” A DTMF digit was detected:
404
+
405
+ ```
406
+ Event: DTMFReceived
407
+ Channel: PJSIP/carrier-00000042
408
+ Digit: 5
409
+ Direction: Received
410
+ ```
411
+
412
+ ### Filtering Events
413
+
414
+ On a busy system, AMI generates thousands of events per minute. Filter them:
415
+
416
+ ```
417
+ Action: Events
418
+ EventMask: call,agent
419
+ ActionID: events-001
420
+
421
+ ```
422
+
423
+ EventMask options: `on` (all events), `off` (no events), or a comma-separated list of event categories. This reduces noise and processing overhead.
424
+
425
+ ---
426
+
427
+ ## Practical AMI Automation Scripts
428
+
429
+ ### Python: Click-to-Dial
430
+
431
+ A script that originates a call between an agent's phone and a customer number:
432
+
433
+ ```python
434
+ import socket
435
+
436
+ class AMIClient:
437
+ def __init__(self, host, port, user, secret):
438
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
439
+ self.sock.connect((host, port))
440
+ self.sock.settimeout(5)
441
+ # Read banner
442
+ self.read_response()
443
+ # Login
444
+ self.send_action({
445
+ 'Action': 'Login',
446
+ 'Username': user,
447
+ 'Secret': secret,
448
+ })
449
+ resp = self.read_response()
450
+ if 'Success' not in resp:
451
+ raise Exception(f"AMI login failed: {resp}")
452
+
453
+ def send_action(self, action):
454
+ msg = ''
455
+ for key, value in action.items():
456
+ msg += f'{key}: {value}\r\n'
457
+ msg += '\r\n'
458
+ self.sock.sendall(msg.encode())
459
+
460
+ def read_response(self):
461
+ data = b''
462
+ while True:
463
+ try:
464
+ chunk = self.sock.recv(4096)
465
+ data += chunk
466
+ if b'\r\n\r\n' in data:
467
+ break
468
+ except socket.timeout:
469
+ break
470
+ return data.decode()
471
+
472
+ def originate(self, channel, context, exten, callerid, variables=None):
473
+ action = {
474
+ 'Action': 'Originate',
475
+ 'Channel': channel,
476
+ 'Context': context,
477
+ 'Exten': exten,
478
+ 'Priority': '1',
479
+ 'CallerID': callerid,
480
+ 'Timeout': '30000',
481
+ 'Async': 'true',
482
+ }
483
+ if variables:
484
+ action['Variable'] = ','.join(f'{k}={v}' for k, v in variables.items())
485
+ self.send_action(action)
486
+ return self.read_response()
487
+
488
+ def hangup(self, channel):
489
+ self.send_action({
490
+ 'Action': 'Hangup',
491
+ 'Channel': channel,
492
+ 'Cause': '16',
493
+ })
494
+ return self.read_response()
495
+
496
+ def close(self):
497
+ self.send_action({'Action': 'Logoff'})
498
+ self.sock.close()
499
+
500
+ # Usage: click-to-dial between agent extension and customer
501
+ ami = AMIClient('127.0.0.1', 5038, 'cron', 'YOUR_SECRET')
502
+
503
+ # First, call the agent's phone
504
+ result = ami.originate(
505
+ channel='PJSIP/agent001',
506
+ context='click-to-dial',
507
+ exten='3125551234',
508
+ callerid='"Click-to-Dial" <8005551234>',
509
+ variables={'customer_name': 'John Smith'}
510
+ )
511
+ print(f"Originate result: {result}")
512
+ ami.close()
513
+ ```
514
+
515
+ ### Node.js: Real-Time Event Monitor
516
+
517
+ A script that connects to AMI and streams call events:
518
+
519
+ ```javascript
520
+ const net = require('net');
521
+
522
+ class AMIEventMonitor {
523
+ constructor(host, port, user, secret) {
524
+ this.client = new net.Socket();
525
+ this.buffer = '';
526
+
527
+ this.client.connect(port, host, () => {
528
+ console.log('Connected to AMI');
529
+ this.send(`Action: Login\r\nUsername: ${user}\r\nSecret: ${secret}\r\n\r\n`);
530
+ });
531
+
532
+ this.client.on('data', (data) => {
533
+ this.buffer += data.toString();
534
+ this.processBuffer();
535
+ });
536
+
537
+ this.client.on('error', (err) => {
538
+ console.error('AMI connection error:', err.message);
539
+ });
540
+ }
541
+
542
+ send(msg) {
543
+ this.client.write(msg);
544
+ }
545
+
546
+ processBuffer() {
547
+ const packets = this.buffer.split('\r\n\r\n');
548
+ this.buffer = packets.pop(); // Keep incomplete packet in buffer
549
+
550
+ for (const packet of packets) {
551
+ if (!packet.trim()) continue;
552
+ const event = {};
553
+ for (const line of packet.split('\r\n')) {
554
+ const idx = line.indexOf(': ');
555
+ if (idx > 0) {
556
+ event[line.substring(0, idx)] = line.substring(idx + 2);
557
+ }
558
+ }
559
+ this.handleEvent(event);
560
+ }
561
+ }
562
+
563
+ handleEvent(event) {
564
+ if (event.Event === 'Newchannel') {
565
+ console.log(`NEW CALL: ${event.CallerIDNum} -> Channel: ${event.Channel}`);
566
+ } else if (event.Event === 'Hangup') {
567
+ console.log(`HANGUP: ${event.Channel} (Cause: ${event['Cause-txt']})`);
568
+ } else if (event.Event === 'BridgeEnter') {
569
+ console.log(`BRIDGE: ${event.Channel} joined ${event.BridgeUniqueid}`);
570
+ } else if (event.Response === 'Success') {
571
+ console.log(`AUTH: ${event.Message}`);
572
+ // Filter to only call events
573
+ this.send('Action: Events\r\nEventMask: call\r\n\r\n');
574
+ }
575
+ }
576
+ }
577
+
578
+ const monitor = new AMIEventMonitor('127.0.0.1', 5038, 'monitoring', 'YOUR_SECRET');
579
+ ```
580
+
581
+ ### Bash: Quick Channel Count Check
582
+
583
+ A one-liner for monitoring scripts:
584
+
585
+ ```bash
586
+ #!/bin/bash
587
+ # Count active channels via AMI
588
+ CHANNELS=$(echo -e "Action: Login\r\nUsername: cron\r\nSecret: YOUR_SECRET\r\n\r\nAction: CoreShowChannels\r\n\r\nAction: Logoff\r\n\r\n" | nc -w 3 127.0.0.1 5038 | grep "ListItems:" | awk '{print $2}')
589
+ echo "Active channels: $CHANNELS"
590
+ ```
591
+
592
+ ---
593
+
594
+ ## AMI Security
595
+
596
+ ### Bind to Localhost
597
+
598
+ Unless you have a specific need for remote AMI access, keep `bindaddr = 127.0.0.1`. If you need remote access, use SSH tunneling instead:
599
+
600
+ ```bash
601
+ ssh -L 5038:127.0.0.1:5038 user@asterisk-server
602
+ ```
603
+
604
+ Then connect your client to `127.0.0.1:5038` on your local machine.
605
+
606
+ ### Unique Secrets Per User
607
+
608
+ Every AMI user should have a different secret. If you use the same secret for all users, you can't revoke one user's access without changing everyone's credentials.
609
+
610
+ ### Minimal Permissions
611
+
612
+ Give each AMI user only the permissions they need:
613
+ - Monitoring system: `read=system,call` only
614
+ - Click-to-dial: `read=call`, `write=originate`
615
+ - VICIdial cron (needs everything): Full permissions
616
+
617
+ ### Firewall
618
+
619
+ If AMI is bound to `0.0.0.0`, firewall it:
620
+
621
+ ```bash
622
+ iptables -A INPUT -p tcp --dport 5038 -s 10.0.0.0/24 -j ACCEPT
623
+ iptables -A INPUT -p tcp --dport 5038 -j DROP
624
+ ```
625
+
626
+ Only allow connections from known internal IPs.
627
+
628
+ ### Audit Logging
629
+
630
+ Enable `displayconnects = yes` in manager.conf to log all AMI connections. Review these logs periodically.
631
+
632
+ ---
633
+
634
+ ## VICIdial-Specific AMI Patterns
635
+
636
+ VICIdial's backend scripts use AMI in specific patterns worth understanding:
637
+
638
+ ### How VICIdial Dials Calls
639
+
640
+ The `VDauto_dial_CAMPAIGN.pl` cron script reads the [dial hopper](/blog/vicidial-dial-hopper-guide/), picks leads, and issues AMI Originate commands with Application: `MeetMe`. The call goes to a MeetMe conference room. When the call is answered (detected by answer supervision or AMD), VICIdial routes an available agent into the same MeetMe room via another AMI Originate.
641
+
642
+ ### How Agent Monitoring Works
643
+
644
+ When a supervisor clicks "Listen" on the real-time report, VICIdial sends an AMI Originate to the supervisor's phone, connecting them to the same MeetMe conference room as the agent. The `q` flag on MeetMe means the supervisor enters in listen-only mode.
645
+
646
+ ### How DTMF Injection Works
647
+
648
+ The agent screen's DTMF button triggers an AMI `AGI` action that runs `agi-dtmf.agi` on the customer channel. This script uses the `EXEC SendDTMF` command within the AGI session to inject digits into the correct call leg.
649
+
650
+ Understanding these patterns helps when troubleshooting VICIdial issues β€” many "VICIdial bugs" are AMI configuration or permission problems.
651
+
652
+ ---
653
+
654
+ ## When AMI Isn't Enough: ARI
655
+
656
+ Asterisk 12+ includes the Asterisk REST Interface (ARI), a more modern HTTP/WebSocket-based API. ARI gives you finer-grained control over individual channels, bridges, and recordings.
657
+
658
+ For new development, ARI is the recommended interface. AMI is still fully supported and VICIdial relies on it entirely, so you'll need AMI knowledge for VICIdial administration regardless.
659
+
660
+ For projects that need both VICIdial integration (AMI) and custom call control (ARI), they can coexist on the same Asterisk instance. Just be careful about conflicting channel manipulations β€” if VICIdial owns a channel through AMI and your ARI application tries to manipulate the same channel, things get unpredictable.
661
+
662
+ ---
663
+
664
+ ## Getting AMI Right
665
+
666
+ AMI is the power tool that makes Asterisk programmable and VICIdial possible. Most VICIdial admins never touch it directly because VICIdial abstracts it away. But when you need custom integrations, automated call flows, or advanced monitoring β€” AMI is where you go.
667
+
668
+ ---
669
+
670
+ ---
671
+
672
+ **Related reading:**
673
+ - [Asterisk Manager Interface (AMI): The Commands You'll Actually Use](https://vicistack.com/blog/asterisk-ami-commands-guide/)
674
+ - [Asterisk Manager Interface (AMI): The Complete Developer Guide](https://vicistack.com/blog/asterisk-manager-interface-guide/)
675
+ - [Asterisk PJSIP TLS Broken After OpenSSL 3 Upgrade? Here's the Fix for 'Wrong Curve' and Every Other Handshake Failure](https://vicistack.com/blog/asterisk-pjsip-tls-openssl3-guide/)
676
+ - [Asterisk Observability with OpenTelemetry and Grafana](https://vicistack.com/blog/asterisk-otel-observability/)
677
+ ## AMI Troubleshooting
678
+
679
+ ### "Connection Refused on Port 5038"
680
+
681
+ ```bash
682
+ # Check if AMI is listening
683
+ ss -tlnp | grep 5038
684
+ ```
685
+
686
+ If nothing shows, AMI isn't enabled. Check `manager.conf`:
687
+ - `enabled = yes` must be set in `[general]`
688
+ - `bindaddr` must match where you're connecting from
689
+ - Reload: `asterisk -rx "manager reload"`
690
+
691
+ ### "Authentication Failed"
692
+
693
+ ```bash
694
+ # Verify the user exists in manager.conf
695
+ asterisk -rx "manager show users"
696
+ ```
697
+
698
+ Check that:
699
+ - Username and secret match exactly (case-sensitive)
700
+ - The user isn't disabled
701
+ - The connecting IP is allowed (check `permit`/`deny` ACLs in the user section)
702
+
703
+ ### "Permission Denied" on Specific Actions
704
+
705
+ AMI permission errors mean the user's `write` permissions don't include the required category. For Originate, you need `write = originate`. For Command, you need `write = command`. For Redirect, you need `write = call`.
706
+
707
+ Check current permissions:
708
+ ```bash
709
+ asterisk -rx "manager show user cron"
710
+ ```
711
+
712
+ ### Events Not Arriving
713
+
714
+ If you're connected and authenticated but not receiving events:
715
+ 1. Check your EventMask β€” if set to `off`, no events will arrive
716
+ 2. Verify `read` permissions include the event categories you expect
717
+ 3. Check your socket buffer β€” if you're not reading fast enough, the TCP buffer fills and events are dropped
718
+ 4. On busy systems (200+ concurrent calls), filter events aggressively with EventMask to avoid overwhelming your client
719
+
720
+ ### AMI Performance at Scale
721
+
722
+ On a 200-agent VICIdial system, AMI handles thousands of actions per minute. Performance considerations:
723
+
724
+ - Each AMI connection consumes an Asterisk thread. Limit concurrent connections to 5-10
725
+ - Use a single persistent connection instead of connecting/disconnecting per action
726
+ - Filter events with EventMask β€” receiving all events on a busy system generates 50+ MB/hour of data
727
+ - Use Async:true for Originate actions to avoid blocking the AMI connection for 30+ seconds per call
728
+
729
+ ### Common AMI Client Libraries
730
+
731
+ Rather than implementing raw TCP socket handling, use a library for your language:
732
+
733
+ **Python**: `panoramisk` or `starpy` β€” both handle connection management, authentication, and event parsing. Panoramisk supports async/await patterns for modern Python.
734
+
735
+ **Node.js**: `asterisk-manager` (npm) β€” provides an event emitter pattern that works well with Node's event loop.
736
+
737
+ **PHP**: `PAMI` (PHP [Asterisk Manager Interface](/blog/vicidial-custom-mysql-reports/)) β€” the most mature PHP AMI library.
738
+
739
+ **Go**: `ari-proxy` or `goami` β€” for high-performance AMI integrations.
740
+
741
+ Using a library eliminates the boilerplate of TCP connection management, packet parsing, and authentication handling. Focus on your business logic instead of protocol details.
742
+
743
+ If you're building AMI integrations for your call center and need the kind of deep Asterisk expertise that comes from hundreds of deployments, that's what [ViciStack](https://vicistack.com/) offers. We've built AMI-based automation for everything from click-to-dial CRM integrations to real-time quality monitoring dashboards. The $5K engagement covers your specific integration needs, not generic documentation.
744
+
745
+ ## Resources
746
+
747
+ - [Read the full article](https://vicistack.com/blog/asterisk-ami-commands-guide/) on ViciStack
748
+ - [ViciStack](https://vicistack.com) - VICIdial hosting and optimization
749
+ - [Free VICIdial Audit](https://vicistack.com/free-audit/)