For the complete documentation index, see llms.txt. This page is also available as Markdown.

AMI / AJAM

Interacting with Asterisk via AMI (TCP 5038) and AJAM (HTTP).

MikoPBX talks to Asterisk through two manager interfaces:

  • AMI (Asterisk Manager Interface) — a line-oriented TCP protocol on port 5038. This is the primary channel used by core workers and modules to send commands (Originate, Hangup, Redirect, …) and to subscribe to events (Newchannel, Hangup, UserEvent, …).

  • AJAM (Asterisk JavaScript Asynchronous Manager) — the same manager actions, exposed over HTTP (default port 8088, TLS 8089) under the /asterisk/ path prefix. Convenient for shell scripts, web clients, and ad-hoc testing with curl.

From PHP you almost never open the socket yourself. You ask the DI container for a ready, authenticated AsteriskManager instance. Raw protocol (telnet / curl) is useful for debugging and for one-off operations from the PBX shell.

Running example. Throughout the developer docs we follow a fictional module ModuleBlackList (config class BlackListConf, main class BlackListMain, model BlackListNumbers). Where this page shows a module calling AMI, imagine it lives in BlackListMain. For a working, real-world AMI consumer pattern see the cookbook page Interact with AMI.

AsteriskManager: the AMI client

The AMI client is MikoPBX\Core\Asterisk\AsteriskManager.

Core/src/Core/Asterisk/AsteriskManager.php
namespace MikoPBX\Core\Asterisk;

class AsteriskManager
{
    public function connect(
        ?string $server = null,
        ?string $username = null,
        ?string $secret = null,
        string $events = 'on'
    ): bool;

    public function disconnect(): void;
    public function loggedIn(): bool;
}

The constructor accepts an optional path to a config file (parsed with parse_ini_file) and an $optconfig array. When values are missing it falls back to defaults: server=localhost, port=5038, username=phpagi, secret=phpagi. connect() accepts a server:port string (e.g. 127.0.0.1:5038), checks that the Asterisk process is actually listening, opens the socket with a 2-second timeout, and logs in. On a successful Login response loggedIn() returns true.

Getting an instance from the DI container

Do not instantiate AsteriskManager directly in module code. Use the helper MikoPBX\Core\System\Util::getAstManager(), which returns a shared, already-connected instance from the DI container.

getAstManager() maps the $events argument to one of two DI services:

$events

DI service name

Provider class

Events

'on'

amiListener

MikoPBX\Common\Providers\AmiConnectionListener

Subscribed

'off'

amiCommander

MikoPBX\Common\Providers\AmiConnectionCommand

Suppressed

Both providers register a shared service that connects to 127.0.0.1:{AMI_PORT} and logs in as the internal phpagi user (the null username/secret fall through to the constructor defaults). amiCommander connects with events='off' so a command-only client is never blocked draining the event stream; amiListener connects with events='on' for code that needs to consume events.

Sending commands

Two low-level primitives back every action:

  • sendRequest(string $action, array $parameters = []): array — writes the action and blocks on waitResponse() until a Response: block arrives. Use for request/response actions.

  • sendRequestTimeout(string $action, array $parameters = []): array — same, but transparently reconnects on a dead socket and returns [] on timeout. Used internally by the list/status helpers below. It auto-fills an ActionID of "{$action}_" . getmypid().

The raw CLI bridge:

  • Command(string $command, ?string $actionid = null): array — runs an Asterisk CLI command (the Command AMI action), e.g. core show channels, pjsip show endpoints.

Channel and call operations

GetChannels(bool $group = true): array wraps the CoreShowChannels action. With $group = true (default) it returns channels grouped by Linkedid; with false it returns a flat list of channel names.

Originate a call from extension 201 to 203 (the BlackList module might do this to ring an administrator):

PJSIP helpers

These call PJSIP manager actions and normalize the multi-event responses into plain arrays:

getPjSipRegistry() issues PJSIPShowRegistrationsOutbound and returns rows of id, state, host, username for each outbound provider registration.

Queues

Call recording

MixMonitor records both legs of a call into a single mixed file and is the preferred recorder; Monitor records the legs separately. MikoPBX uses MixMonitor for standard call recording.

Working with events

Register handlers, then enter a blocking loop that dispatches each incoming event to the matching handler.

  • addEventHandler($event, $callback) registers a handler. $event is lower-cased internally, so 'Hangup' and 'hangup' are equivalent; '*' is a catch-all. $callback must be an array callable [$object, 'method'] (or a plain function-name string) — see the warning below. It returns false if a handler for that event is already registered.

  • Events(string $eventMask) turns the event stream on/off at runtime ('on', 'off', or a mask such as 'system,call,log').

  • UserEvent(string $name, array $headers) emits a custom UserEvent — the standard way for an AGI script or worker to push a message onto the AMI bus.

  • waitUserEvent() is a blocking loop tuned for UserEvent consumers; it also supports an idle callback (setOnIdleCallback()).

A minimal event-listening loop, of the kind a long-running worker uses. Note that the handlers are methods on the worker object, registered as array callables:

A blocking event loop belongs in a background worker, not in a web request or a config generator. See the workers documentation in Workers for how to run one as a managed process, and Debugging a PHP worker for attaching to it.

manager.conf — how AMI is configured

The manager.conf file is generated by MikoPBX\Core\Asterisk\Configs\ManagerConf.

Key facts to internalize:

  • The [general] section (with enabled = yes and port = {AMI_PORT}) is written unconditionally. The AMI listener is always on.

  • PbxSettings::AMI_PORT (key AMIPort, default 5038) sets the TCP port.

  • PbxSettings::AMI_ENABLED (key AMIEnabled, default 1) gates only the block that writes the database-defined AsteriskManagerUsers. When it is '1', each enabled manager user becomes a manager.conf section with its secret, permit/deny ACLs, and read/write class permissions. The internal phpagi user is always appended regardless of this setting.

  • webenabled = yes is what allows the same manager actions to be reached over HTTP (AJAM).

Module hook: generateManagerConf

A module can append its own manager.conf sections by implementing the generateManagerConf hook. ManagerConf collects every module's contribution via the hook constant AsteriskConfigInterface::GENERATE_MANAGER_CONF:

So BlackListConf could expose a dedicated manager user by returning an extra section:

The generateManagerConf hook constant and the generateConfig()generateConfigProtected()saveConfig() flow are documented with the other hooks in the module class reference.

After regeneration, ManagerConf::reload() rewrites both manager.conf and http.conf and runs module reload manager / module reload http in Asterisk.

AMI users: AsteriskManagerUsers

Database-defined AMI accounts are the model MikoPBX\Common\Models\AsteriskManagerUsers, backed by table m_AsteriskManagerUsers. Each row carries the username, secret, a networkfilterid (linking to a NetworkFilters row that provides the permit/deny ACLs), per-class permissions (call, cdr, originate, reporting, agent, config, dialplan, dtmf, log, system, command, verbose, user, each read/write/readwrite/none), and optional eventfilter lines. Setting networkfilterid to localhost forces a localhost-only ACL.

http.conf — how AJAM is configured

AJAM rides on the Asterisk built-in HTTP server, configured by MikoPBX\Core\Asterisk\Configs\HttpConf.

  • The HTTP server is enabled when either AJAM_ENABLED (key AJAMEnabled, default 1) or ARI_ENABLED (key ARIEnabled, default 0) is on.

  • PbxSettings::AJAM_PORT (key AJAMPort, default 8088) is the HTTP port; PbxSettings::AJAM_PORT_TLS (key AJAMPortTLS, default 8089) is the HTTPS port. When a TLS port is set, HttpConf provisions the certificate via SslCertificateService::prepareAsteriskCertificates('asterisk-http') and writes tlsenable/tlsbindaddr/tlscertfile/tlsprivatekey.

  • prefix=asterisk is what makes every AJAM URL start with /asterisk/ — e.g. /asterisk/rawman and /asterisk/mxml. Keep this prefix in mind when crafting curl calls.

HttpConf::reload() regenerates http.conf and runs module reload http.

Raw protocol examples

These run from the PBX shell (connect via SSH first). They use the localhost-only internal account; replace ${PBX_HOST} with a remote address only if you have an AsteriskManagerUsers record permitting it.

Originate

Connect to the AMI socket:

Authenticate:

Originate a call from 201 to 203:

  • SIPADDHEADER — header added to the INVITE sent to extension 201.

  • pt1c_cid — destination number.

  • ALLOW_MULTY_ANSWER — if 201 has multiple registrations, SIPADDHEADER is sent to all contacts.

AJAM exposes the same actions over HTTP under the /asterisk/ prefix:

Build the call file:

Move it into the Asterisk outgoing spool directory (Asterisk processes and removes it). The spool root is the resolved media mount point (Directories::getDir(Directories::AST_SPOOL_DIR), i.e. <mountpoint>/mikopbx/astspool); on a typical USB-disk install the mount point is /storage/usbdisk1:

Redirect (blind transfer)

Forwarding without consultation:

  • PJSIP/201-000001 — channel to forward.

  • internal-transfer — always use this context for redirects.

  • 203 — destination number.

  • The channel that was bridged with PJSIP/201-000001 is hung up.

Attended transfer

Call forwarding with consultation (raw Atxfer action — there is no AsteriskManager wrapper for it):

  • PJSIP/201-000001 — the channel that transfers the call; it is connected to 203 for consultation.

  • When PJSIP/201-000001 hangs up, the bridged channel is connected to 203.

See also

Last updated

Was this helpful?