Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ami React Laravel Package

clue/ami-react

Async Asterisk Manager Interface (AMI) client built on ReactPHP. Connect over TCP, send actions like Originate or CoreShowChannels, and handle responses and events asynchronously via promises, with a simple event-driven API for monitoring calls and channels in real time.

Deep Wiki
Context7

Getting Started

Install with Composer and make sure your Asterisk manager.conf has a dedicated AMI user before writing any code:

composer require clue/ami-react
; /etc/asterisk/manager.conf
[general]
enabled = yes
port = 5038
bindaddr = 127.0.0.1

[myapp]
secret = change-me
read = all
write = all
permit = 127.0.0.1/255.255.255.255

Reload with asterisk -rx "manager reload", then start with the smallest possible listener:

<?php

require __DIR__ . '/vendor/autoload.php';

use Clue\React\Ami\Factory;
use Clue\React\Ami\Protocol\Action;
use Clue\React\Ami\Protocol\Event;

$factory = new Factory();

$factory->createClient('myapp:[email protected]:5038')->then(
    function ($client) {
        $client->on('event', function (Event $event) {
            printf(
                "%s channel=%s\n",
                $event->getName(),
                $event->getFieldValue('Channel') ?? '-'
            );
        });

        // Events are off by default on a new AMI session.
        $client->getEvents();

        $client->action(new Action('Ping'))->then(function ($response) {
            echo 'Ping response: ' . $response->getFieldValue('Response') . PHP_EOL;
        });
    },
    function (Throwable $e) {
        fwrite(STDERR, 'AMI connection failed: ' . $e->getMessage() . PHP_EOL);
    }
);

Where to look first:

  • README.md and examples/ for the supported connection and action patterns.
  • src/Factory.php for URL parsing, connector/TLS options, and login behavior.
  • src/Client.php for action(), getEvents(), event emission, and close semantics.
  • src/Protocol/Message.php for getFieldValue() / getFields(); Action, Response, and Event build on it.

The first useful production use case is usually a long-running event tail: watch Newchannel, Dial, Bridge, Hangup, and Cdr to build a live call view, or subscribe to queue/agent events for wallboard data. Remember that the process stays alive while the connection and loop are active; use Ctrl-C or call $client->close() to stop it.

Implementation Patterns

Wrap AMI actions behind small helper methods. The package exposes raw AMI actions, so keep protocol details out of your domain code:

use Clue\React\Ami\Client;
use Clue\React\Ami\Protocol\Action;
use React\Promise\PromiseInterface;

function amiAction(Client $client, string $name, array $fields = []): PromiseInterface
{
    return $client->action(new Action($name, $fields));
}

amiAction($client, 'CoreStatus')->then(function ($response) {
    echo $response->getFieldValue('CoreCurrentCalls') . PHP_EOL;
});

Treat the connection as an event bus, not a request/response database. Keep an in-memory index keyed by Uniqueid or Linkedid, update it from events, and prune it on Hangup / Cdr:

$calls = [];

$client->on('event', function (Event $event) use (&$calls) {
    $name = $event->getName();
    $uniqueid = $event->getFieldValue('Uniqueid');

    if ($name === 'Newchannel' && $uniqueid) {
        $calls[$uniqueid] = [
            'channel' => $event->getFieldValue('Channel'),
            'state'   => $event->getFieldValue('ChannelStateDesc'),
        ];
    }

    if ($name === 'Hangup' && $uniqueid) {
        unset($calls[$uniqueid]);
    }
});

Filter events at the source. AMI can be extremely chatty. Use EventMask rather than receiving everything and discarding it in PHP:

$client->action(new Action('Events', [
    'EventMask' => 'call,agent,queue',
]));

If you need everything, expect high event volume on busy systems and design handlers accordingly.

Reconnect explicitly. There is no built-in reconnect loop. Treat close as normal and recreate the client with backoff:

use React\EventLoop\Loop;

function connect(Factory $factory, string $url, callable $onClient): void
{
    $factory->createClient($url)->then($onClient, function (Throwable $e) use (&$retry) {
        // log and retry with backoff
    });

    // On the client:
    // $client->on('close', fn () => reconnect with backoff);
}

After reconnecting, re-enable events and re-register handlers. Any pending action promises from the old connection need your own timeout/rejection handling.

Add keepalives. Idle TCP connections can be dropped by firewalls, NAT, or Asterisk restarts. A periodic Ping action gives you a cheap health check and keeps the session active:

Loop::addPeriodicTimer(30, function () use ($client) {
    $client->action(new Action('Ping'))->then(null, function () {
        $client->close();
    });
});

Run it as a daemon, not in a web request. In Laravel, put the AMI listener in an Artisan command and run it under Supervisor. Bind the ReactPHP loop/factory in a service provider if useful, but do not open an AMI connection per HTTP request. High-volume event handlers should push work onto Laravel queues rather than writing to the database or making HTTP calls inline.

Shut down gracefully. Register SIGINT / SIGTERM, remove timers, close the client, and let the loop exit. This matters for Supervisor restarts and deploy scripts.

Test the mapping, not the socket. Extract event-to-domain mapping into pure functions and test those directly. For integration tests, run against a fake AMI server or a local Asterisk instance; do not point tests at production PBX credentials.

Gotchas and Tips

  • Events are disabled by default. A freshly authenticated AMI session does not stream events until you send Events: on (or use the client's event-enabling helper). If your listener is silent, this is the first thing to check.
  • Action errors are still responses. AMI often returns Response: Error with a Message field rather than a transport-level failure. Inspect the response fields (Response, Message) instead of assuming a resolved promise means success.
  • Permissions are per-manager-user. A user missing read or write permissions will connect successfully but fail individual actions or miss events. Check manager.conf, manager show user <name>, and Asterisk logs.
  • URL credentials need care. Special characters in secrets can break user:pass@host:port parsing. URL-encode them or keep credentials in config and construct the URL carefully; never commit real secrets.
  • Default AMI is plaintext. Use tls://host:5039 only if Asterisk is configured for AMI TLS. Do not assume port 5038 supports TLS.
  • A thrown exception inside an event handler can terminate the loop. Wrap handler bodies in try/catch and log; one malformed event should not kill your daemon.
  • Blocking work stalls everything. A slow database write, synchronous HTTP call, or sleep() inside an event handler blocks all AMI reads and timers. Offload it to a queue or child process.
  • No automatic reconnection or action timeout. Implement both. A promise that never resolves after a half-open connection is otherwise undetectable.
  • Multi-message responses exist. Some AMI actions, especially command-style and status-style actions, produce more than one message. Do not assume every action resolves with a single simple response; inspect the actual shape for actions like Command or QueueStatus.
  • Use ActionID deliberately. If the client or your wrapper assigns action IDs, keep them unique. If you generate your own, use them to correlate responses and avoid collisions across reconnects.
  • Long-running memory growth is real. Maps keyed by Uniqueid, listener arrays, and caches will grow. Prune on terminal events and consider periodic worker restarts.
  • This is a thin, low-level package. Expect AMI wire-format names (Field: value) to leak into your code. It is not a full Asterisk domain model, so build a small adapter layer; that also isolates you from package changes.
  • Small ecosystem, so pin versions and read the source. With a niche package like this, the source and examples are often more authoritative than blog posts. Check compatibility with your ReactPHP version and monitor issue activity before making it a core dependency.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr
laravel2580/idehelpers
delatbabel/viewpages
anavel/gettext
muhamadrezaar/highcharts
delatbabel/keylists
frankkessler/salesforce-laravel-oauth2-rest
anavel/foundation
wdmtech/enviricons
eii/laravel-captcha
muka/shape-reader
tdt/json