File size: 24,729 Bytes
2f437f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
---
license: mit
tags:
  - vicidial
  - call-center
  - asterisk
  - manager
  - interface
---

# Asterisk Manager Interface (AMI): The Complete Developer Guide

**Last updated: March 2026 | Reading time: ~24 minutes** The [Asterisk Manager Interface](/blog/voip-mos-score-guide/) is a TCP socket API that lets you control Asterisk programmatically. Originate calls. Monitor channels. Transfer calls. Get real-time events for every call that enters, bridges, and hangs up on your system. If you're building anything on top of Asterisk β€” a CRM integration, a wallboard, a click-to-call feature, a custom reporting tool β€” AMI is probably how you're going to do it. VICIdial itself uses AMI heavily. The agent screen, the real-time report, the auto-dial engine β€” they all talk to Asterisk through AMI. This guide covers everything from initial setup to production-grade integrations. No fluff. Code that works. --- Asterisk has three interfaces for external programs. People confuse them constantly. **AMI ([Manager Interface](/blog/asterisk-manager-interface-guide/))** β€” TCP socket on port 5038. Send commands, receive events. Best for: monitoring, originating calls, managing channels, building dashboards. Think of it...

## Overview

**Last updated: March 2026 | Reading time: ~24 minutes**

The [Asterisk Manager Interface](/blog/voip-mos-score-guide/) is a TCP socket API that lets you control Asterisk programmatically. Originate calls. Monitor channels. Transfer calls. Get real-time events for every call that enters, bridges, and hangs up on your system.

If you're building anything on top of Asterisk β€” a CRM integration, a wallboard, a click-to-call feature, a custom reporting tool β€” AMI is probably how you're going to do it. VICIdial itself uses AMI heavily. The agent screen, the real-time report, the auto-dial engine β€” they all talk to Asterisk through AMI.

This guide covers everything from initial setup to production-grade integrations. No fluff. Code that works.

---

## AMI vs. ARI vs. AGI: Which One Do You Need?

Asterisk has three interfaces for external programs. People confuse them constantly.

**AMI ([Manager Interface](/blog/asterisk-manager-interface-guide/))** β€” TCP socket on port 5038. Send commands, receive events. Best for: monitoring, originating calls, managing channels, building dashboards. Think of it as the "admin remote control."

**AGI (Asterisk Gateway Interface)** β€” Runs a script during a live call. The dialplan hits an AGI application, which forks a process (or connects to a FastAGI server), and that process controls the call step by step. Best for: IVR logic, call routing decisions, database lookups during a call. Think of it as "dialplan scripting."

**ARI (Asterisk REST Interface)** β€” HTTP/WebSocket API introduced in Asterisk 12. Best for: building entire telephony applications where you want full control of every call. Stasis applications. Think of it as "build your own PBX."

For VICIdial integrations, AMI is what you want 95% of the time. VICIdial doesn't use ARI. AGI is used for specific call-flow customizations. But AMI is the backbone.

---

## Setting Up manager.conf

The AMI configuration lives in `/etc/asterisk/manager.conf`. On a fresh Asterisk install, it's usually there but disabled.

```ini
; /etc/asterisk/manager.conf

[general]
enabled = yes
port = 5038
bindaddr = 127.0.0.1    ; IMPORTANT: Only listen on localhost by default
displayconnects = yes
timestampevents = yes

[vicistack]
secret = a_strong_random_password_here
deny = 0.0.0.0/0.0.0.0
permit = 127.0.0.1/255.255.255.255
read = system,call,log,verbose,agent,user,config,dtmf,reporting,cdr,dialplan
write = system,call,agent,user,config,command,reporting,originate
writetimeout = 5000
```

Let's break down the important parts.

### bindaddr

This controls what IP address AMI listens on. **Never set this to 0.0.0.0 on a production system** unless you have firewall rules restricting access. AMI has no rate limiting, no brute force protection, and if someone gets in, they can originate calls, execute shell commands (yes, really), and read your entire Asterisk configuration.

```ini
; GOOD: Only local connections
bindaddr = 127.0.0.1

; ACCEPTABLE: Specific internal network interface
bindaddr = 10.0.0.5

; DANGEROUS: Listen on all interfaces
; bindaddr = 0.0.0.0
```

If you need remote AMI access (e.g., your web server is on a different box than Asterisk), use an SSH tunnel or a VPN. Don't expose port 5038 to the internet.

### User Permissions

The `read` and `write` lines control what the AMI user can do. These are comma-separated permission classes:

| Class | Read (receive events) | Write (send actions) |
|-------|----------------------|---------------------|
| system | System events | Restart, shutdown |
| call | Call events | Originate, redirect, hangup |
| log | Log events | β€” |
| verbose | Verbose messages | β€” |
| agent | Agent events | Agent login/logoff |
| user | User events | UserEvent |
| config | β€” | GetConfig, UpdateConfig |
| dtmf | DTMF events | β€” |
| reporting | CEL/CDR events | β€” |
| cdr | CDR events | β€” |
| dialplan | Dialplan events | β€” |
| command | β€” | Execute CLI commands |
| originate | β€” | Originate calls |

**Principle of least privilege.** If your integration only needs to monitor calls, give it `read = call,cdr` and `write = ` (nothing). If it only needs to originate calls, give it `write = originate` and nothing else.

The `command` permission is particularly dangerous β€” it lets the AMI user run arbitrary Asterisk CLI commands, some of which can execute shell commands. Only grant `command` to trusted integrations running on the same server.

### IP Restrictions

Always use `deny` and `permit` to restrict which IPs can connect:

```ini
[monitoring_user]
secret = monitor_password_here
deny = 0.0.0.0/0.0.0.0           ; Deny everything first
permit = 127.0.0.1/255.255.255.255   ; Allow localhost
permit = 10.0.1.50/255.255.255.255   ; Allow the monitoring server
read = call,cdr,agent
write =
```

After editing manager.conf, reload:

```bash
asterisk -rx "manager reload"
```

---

## Connecting and Authenticating

AMI is a plain TCP socket. You can test it with telnet:

```bash
telnet localhost 5038
```

You'll see:

```
Asterisk Call Manager/7.0.3
```

Now authenticate:

```
Action: Login
Username: vicistack
Secret: a_strong_random_password_here

```

(Note the blank line at the end β€” AMI uses blank lines as message terminators.)

Response:

```
Response: Success
Message: Authentication accepted
```

You're in. Now you can send actions and receive events.

### Authentication Failure

If you get:

```
Response: Error
Message: Authentication failed
```

Check:
1. Username matches a section name in manager.conf (case-sensitive)
2. Secret matches exactly
3. Your IP is in the permit list
4. You reloaded manager after editing the config

---

## Core AMI Actions

### Originate β€” Make a Call

The most used AMI action. This tells Asterisk to call a number and connect it to an extension, application, or another number.

```
Action: Originate
Channel: SIP/carrier/18005551234
Context: from-internal
Exten: 100
Priority: 1
CallerID: "Sales" <18005559999>
Timeout: 30000
ActionID: call-001

```

This tells Asterisk: "Call 18005551234 through the SIP trunk named 'carrier'. When they answer, send them to extension 100 in the from-internal context."

For click-to-call (agent clicks a number in CRM, phone rings, then the lead is dialed):

```
Action: Originate
Channel: SIP/agent_phone_100
Context: click-to-call
Exten: 18005551234
Priority: 1
CallerID: "Agent Smith" <18005559999>
Timeout: 30000
ActionID: c2c-agent100-001

```

This calls the agent's phone first. When the agent picks up, Asterisk dials the lead number.

### Status β€” Get Channel Information

```
Action: Status
ActionID: status-001

```

Returns a list of all active channels with caller ID, state, duration, context, and extension. This is what powers real-time wallboards.

### Command β€” Run CLI Commands

```
Action: Command
Command: sip show peers
ActionID: cmd-001

```

Returns the output of the Asterisk CLI command. Use this sparingly β€” it's a text-based response that you have to parse. For structured data, use dedicated actions instead.

### CoreShowChannels β€” Active Call Details

```
Action: CoreShowChannels
ActionID: channels-001

```

Returns structured data about every active channel. Better than `Action: Command / Command: core show channels` because the output is event-based and parseable.

### Hangup β€” Kill a Channel

```
Action: Hangup
Channel: SIP/carrier-00000042
ActionID: hangup-001

```

Immediately hangs up the specified channel. Use the channel name from Status or CoreShowChannels.

### Redirect β€” Transfer a Call

```
Action: Redirect
Channel: SIP/agent_100-00000042
Context: transfer-destination
Exten: 200
Priority: 1
ActionID: xfer-001

```

Transfers the call to extension 200 in the transfer-destination context. This is how VICIdial's blind transfer works under the hood.

### QueueStatus β€” Queue Monitoring

```
Action: QueueStatus
ActionID: queue-001

```

Returns members and callers in all queues. If you're building an inbound call center dashboard, this is the action you'll use most.

---

## AMI Events: Real-Time Call Data

When you connect with `read` permissions, Asterisk streams events to your connection in real time. Here are the events you'll see most:

### Newchannel

Fired when a new channel is created (call starts):

```
Event: Newchannel
Privilege: call,all
Channel: SIP/carrier-00000042
ChannelState: 0
ChannelStateDesc: Down
CallerIDNum: 18005551234
CallerIDName: John Doe
AccountCode:
Exten: s
Context: from-carrier
Uniqueid: 1711408200.42
```

### Dial

Fired when one channel dials another:

```
Event: Dial
SubEvent: Begin
Channel: SIP/carrier-00000042
Destination: SIP/agent_100-00000043
CallerIDNum: 18005551234
CallerIDName: John Doe
DialString: agent_100
```

### Bridge

Fired when two channels are connected (call answered):

```
Event: Bridge
Privilege: call,all
Bridgestate: Link
Bridgetype: core
Channel1: SIP/carrier-00000042
Channel2: SIP/agent_100-00000043
Uniqueid1: 1711408200.42
Uniqueid2: 1711408200.43
CallerID1: 18005551234
CallerID2: 100
```

### Hangup

Fired when a channel is destroyed:

```
Event: Hangup
Channel: SIP/carrier-00000042
Uniqueid: 1711408200.42
CallerIDNum: 18005551234
Cause: 16
Cause-txt: Normal Clearing
```

### AgentCalled / AgentConnect / AgentComplete

Queue-specific events that track agent availability and call handling. Essential for building agent performance dashboards.

---

## Building a Real AMI Client

Telnet is fine for testing. For production, you need a real client with connection management, event parsing, and error handling.

### Python Example

```python
import socket
import time


class AMIClient:
    def __init__(self, host='127.0.0.1', port=5038):
        self.host = host
        self.port = port
        self.sock = None
        self.buffer = ''

    def connect(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(30)
        self.sock.connect((self.host, self.port))
        # Read the banner
        banner = self._read_response()
        return banner

    def login(self, username, secret):
        self._send_action({
            'Action': 'Login',
            'Username': username,
            'Secret': secret,
        })
        response = self._read_response()
        if 'Authentication accepted' not in response:
            raise Exception(f'AMI login failed: {response}')
        return response

    def originate(self, channel, context, exten, priority=1,
                  callerid=None, timeout=30000, action_id=None):
        action = {
            'Action': 'Originate',
            'Channel': channel,
            'Context': context,
            'Exten': exten,
            'Priority': str(priority),
            'Timeout': str(timeout),
        }
        if callerid:
            action['CallerID'] = callerid
        if action_id:
            action['ActionID'] = action_id
        self._send_action(action)
        return self._read_response()

    def command(self, cmd, action_id=None):
        action = {
            'Action': 'Command',
            'Command': cmd,
        }
        if action_id:
            action['ActionID'] = action_id
        self._send_action(action)
        return self._read_response()

    def logoff(self):
        self._send_action({'Action': 'Logoff'})

    def _send_action(self, action):
        msg = ''
        for key, value in action.items():
            msg += f'{key}: {value}\r\n'
        msg += '\r\n'
        self.sock.sendall(msg.encode('utf-8'))

    def _read_response(self):
        while '\r\n\r\n' not in self.buffer:
            chunk = self.sock.recv(4096).decode('utf-8')
            if not chunk:
                raise ConnectionError('AMI connection closed')
            self.buffer += chunk
        response, self.buffer = self.buffer.split('\r\n\r\n', 1)
        return response

    def close(self):
        if self.sock:
            self.logoff()
            self.sock.close()


# Usage
ami = AMIClient('127.0.0.1', 5038)
ami.connect()
ami.login('vicistack', 'your_password')

# Check SIP peers
result = ami.command('sip show peers')
print(result)

# Originate a call
ami.originate(
    channel='SIP/carrier/18005551234',
    context='from-internal',
    exten='100',
    callerid='"Sales" <18005559999>',
    action_id='test-call-001'
)

ami.close()
```

### Node.js Example

```javascript
const net = require('net');

class AMIClient {
  constructor(host = '127.0.0.1', port = 5038) {
    this.host = host;
    this.port = port;
    this.socket = null;
    this.buffer = '';
    this.callbacks = {};
    this.eventHandlers = {};
  }

  connect() {
    return new Promise((resolve, reject) => {
      this.socket = net.createConnection(this.port, this.host);
      this.socket.setEncoding('utf8');

      this.socket.once('data', (data) => {
        // First data is the banner
        resolve(data.trim());
      });

      this.socket.on('data', (data) => {
        this.buffer += data;
        this._processBuffer();
      });

      this.socket.on('error', reject);
    });
  }

  login(username, secret) {
    return this.sendAction({
      Action: 'Login',
      Username: username,
      Secret: secret,
    });
  }

  originate(channel, context, exten, options = {}) {
    return this.sendAction({
      Action: 'Originate',
      Channel: channel,
      Context: context,
      Exten: exten,
      Priority: options.priority || '1',
      Timeout: options.timeout || '30000',
      CallerID: options.callerId || '',
      ActionID: options.actionId || `orig-${Date.now()}`,
    });
  }

  sendAction(action) {
    return new Promise((resolve) => {
      const actionId = action.ActionID || `act-${Date.now()}`;
      action.ActionID = actionId;
      this.callbacks[actionId] = resolve;

      let msg = '';
      for (const [key, value] of Object.entries(action)) {
        if (value) msg += `${key}: ${value}\r\n`;
      }
      msg += '\r\n';
      this.socket.write(msg);
    });
  }

  on(eventName, handler) {
    if (!this.eventHandlers[eventName]) {
      this.eventHandlers[eventName] = [];
    }
    this.eventHandlers[eventName].push(handler);
  }

  _processBuffer() {
    const messages = this.buffer.split('\r\n\r\n');
    this.buffer = messages.pop(); // Keep incomplete message

    for (const msg of messages) {
      if (!msg.trim()) continue;
      const parsed = {};
      for (const line of msg.split('\r\n')) {
        const idx = line.indexOf(': ');
        if (idx > 0) {
          parsed[line.substring(0, idx)] = line.substring(idx + 2);
        }
      }

      // Route to callback or event handler
      if (parsed.ActionID && this.callbacks[parsed.ActionID]) {
        this.callbacks[parsed.ActionID](parsed);
        delete this.callbacks[parsed.ActionID];
      }
      if (parsed.Event) {
        const handlers = this.eventHandlers[parsed.Event] || [];
        for (const h of handlers) h(parsed);
      }
    }
  }

  close() {
    if (this.socket) {
      this.socket.write('Action: Logoff\r\n\r\n');
      this.socket.end();
    }
  }
}

// Usage
async function main() {
  const ami = new AMIClient('127.0.0.1', 5038);
  await ami.connect();
  const loginResult = await ami.login('vicistack', 'your_password');
  console.log('Login:', loginResult);

  // Listen for call events
  ami.on('Newchannel', (event) => {
    console.log('New call:', event.CallerIDNum, event.Channel);
  });

  ami.on('Hangup', (event) => {
    console.log('Call ended:', event.Channel, event['Cause-txt']);
  });

  // Keep running to receive events
  // ami.close() when done
}

main().catch(console.error);
```

---

## AMI and VICIdial: How They Work Together

VICIdial's relationship with AMI is deep. Here's what's happening behind the scenes:

**Auto-dial engine (VDauto_dial.pl):** Connects to AMI and sends Originate actions for each lead pulled from the hopper. It monitors Newchannel, Bridge, and Hangup events to track call state.

**Agent interface (vicidial.php):** Uses AMI to transfer calls, hang up channels, initiate manual dials, and monitor agent channel state.

**Real-time reporting:** Queries AMI via the Status action and CoreShowChannels to build the real-time agent display.

**Call recording:** Uses AMI's Monitor/MixMonitor actions to start and stop recordings.

If you're building custom integrations with VICIdial, you don't usually talk to AMI directly. VICIdial has its own [Non-Agent API](/blog/vicidial-api-integration/) that wraps AMI actions. But for direct Asterisk integrations, AMI is the way.

### VICIdial's AMI User

On a ViciBox install, VICIdial creates its own AMI user in manager.conf. Don't modify or share this user. Create a separate AMI user for your integrations:

```ini
; Add below the existing VICIdial user
[myintegration]
secret = separate_strong_password
deny = 0.0.0.0/0.0.0.0
permit = 127.0.0.1/255.255.255.255
read = call,agent,cdr
write = originate
```

---

## Production Hardening

### Connection Pooling

AMI connections are persistent TCP sockets. Opening a new connection for each request wastes time on TCP handshake + authentication. Maintain a pool of authenticated connections.

### Automatic Reconnection

Asterisk restarts, network blips, and TCP timeouts will drop your AMI connection. Your client must detect disconnection and automatically reconnect + re-authenticate.

```python
# Simplified reconnection loop
def get_ami_connection():
    while True:
        try:
            ami = AMIClient()
            ami.connect()
            ami.login('user', 'secret')
            return ami
        except Exception as e:
            print(f'AMI connection failed: {e}, retrying in 5s')
            time.sleep(5)
```

### Event Filtering

A busy Asterisk system generates thousands of events per minute. If your integration only cares about Hangup events, filter server-side:

```
Action: Events
EventMask: call

```

Or more granular filtering with Asterisk 16+:

```
Action: Filter
Operation: Add
Filter: Event: Hangup

```

This tells Asterisk to only send you Hangup events, reducing bandwidth and processing overhead.

### Rate Limiting Your Actions

Don't blast AMI with hundreds of Originate actions per second. Asterisk processes AMI actions sequentially per connection. Flooding it will queue up actions and increase latency for all of them.

For high-volume origination (like a predictive dialer), use multiple AMI connections with load distribution. VICIdial does this β€” it maintains multiple connections and distributes originate requests across them.

### Security Considerations

1. **Never expose port 5038 to the internet.** SSH tunnels or VPN for remote access.
2. **Use strong passwords.** AMI has no lockout after failed attempts.
3. **Restrict permissions.** Don't give `command` or `system` write access unless absolutely necessary.
4. **Log AMI connections.** Enable `displayconnects = yes` in manager.conf and monitor `/var/log/asterisk/messages` for login events.
5. **Consider TLS.** Asterisk 16+ supports AMI over TLS. Configure it if your AMI client connects over a network.

```ini
; manager.conf β€” TLS support (Asterisk 16+)
[general]
enabled = yes
port = 5038
tlsenable = yes
tlsbindaddr = 0.0.0.0:5039
tlscertfile = /etc/asterisk/keys/asterisk.pem
tlsprivatekey = /etc/asterisk/keys/asterisk.key
```

---

## Common AMI Pitfalls

**ActionID is your friend.** Always include an ActionID in your actions. AMI responses don't always come back in order, especially with async actions like Originate. The ActionID is the only way to correlate a response with the action that triggered it.

**Originate is async by default.** When you send an Originate action, the Response: Success just means Asterisk accepted the request. It doesn't mean the call connected. You need to listen for Newchannel, Dial, Bridge, and Hangup events to track the call's lifecycle.

```
Action: Originate
Channel: SIP/carrier/18005551234
Context: from-internal
Exten: 100
Priority: 1
Async: true
ActionID: call-001

```

**Buffer carefully.** AMI messages are terminated by `\r\n\r\n`. But messages can arrive fragmented across multiple TCP reads. Your parser must buffer data and only process complete messages.

**Handle multi-line responses.** Some responses (like Command output) span multiple lines. They end with `--END COMMAND--` before the `\r\n\r\n` terminator. Your parser needs to handle this.

**Event floods during peak hours.** On a system with 100+ concurrent calls, AMI can generate 50+ events per second. If your client can't keep up processing events, the TCP buffer fills and you lose data. Use event filtering to only subscribe to events you actually need.

---

## Useful AMI One-Liners

For quick operations from the command line without building a full client:

```bash
# Quick call origination via netcat
echo -e "Action: Login\r\nUsername: user\r\nSecret: pass\r\n\r\nAction: Originate\r\nChannel: SIP/carrier/18005551234\r\nContext: default\r\nExten: 100\r\nPriority: 1\r\n\r\nAction: Logoff\r\n\r\n" | nc localhost 5038

# Check how many active channels
echo -e "Action: Login\r\nUsername: user\r\nSecret: pass\r\n\r\nAction: Command\r\nCommand: core show channels count\r\n\r\nAction: Logoff\r\n\r\n" | nc localhost 5038

# Reload SIP configuration remotely
echo -e "Action: Login\r\nUsername: user\r\nSecret: pass\r\n\r\nAction: Command\r\nCommand: sip reload\r\n\r\nAction: Logoff\r\n\r\n" | nc localhost 5038
```

---

## AMI for Real-Time Dashboards

The killer use case for AMI in a call center is [real-time monitoring](/blog/vicidial-realtime-agent-dashboard/). Here's the event flow for building a dashboard:

1. Connect to AMI, authenticate, subscribe to `call` and `agent` events
2. On **Newchannel**: New call starting, add to active calls list
3. On **Dial**: Call is ringing, show in "ringing" state
4. On **Bridge**: Call answered, start timer, show in "connected" state
5. On **Hangup**: Call ended, calculate duration, remove from active calls
6. On **AgentConnect**: Agent took a call, update agent status
7. On **AgentComplete**: Agent finished a call, show wrap-up time

Feed these events into a WebSocket server and push to a browser-based dashboard. That's exactly how VICIdial's Real-Time Report works, though it goes through PHP + database polling rather than direct WebSocket.

For a more modern approach, push AMI events into Redis or a message queue and have your dashboard consume from there. This decouples the AMI connection from the web layer and handles connection drops gracefully.

---

**Building custom integrations on top of VICIdial?** ViciStack has done this for dozens of operations β€” CRM integrations, custom wallboards, automated quality monitoring, and click-to-call systems. We know where AMI works well and where you'll hit walls. Our engagement is $5K ($1K deposit, $4K on completion) and includes architecture review, implementation guidance, and AMI security hardening. [Let's talk](/contact/).

---

*Related: [VICIdial API Integration](/blog/vicidial-api-integration/) | [VICIdial Grafana Dashboards](/blog/vicidial-grafana-dashboards/) | [VICIdial Asterisk Configuration](/blog/vicidial-asterisk-configuration/) | [VICIdial CRM Integration](/blog/vicidial-crm-integration/)*

## Resources

- [Read the full article](https://vicistack.com/blog/asterisk-manager-interface-guide/) on ViciStack
- [ViciStack](https://vicistack.com) - VICIdial hosting and optimization
- [Free VICIdial Audit](https://vicistack.com/free-audit/)