Asterisk

Asterisk Originate: Click-to-Call from the CLI, AMI and Call Files

Himanshu Pal

Himanshu Pal

Making Asterisk place the call

Click-to-call, automated reminders, callback requests and outbound campaigns all rest on the same primitive: telling Asterisk to originate a call rather than waiting for one to arrive. Asterisk exposes this three ways — the CLI, the Manager Interface, and call files — and they share one mental model.

That model is the part people get wrong at first. An originate has two legs. Asterisk first calls a channel (leg A) and, once that answers, connects it to something (leg B) — either a dialplan extension or an application. You are not placing one call; you are creating one and then deciding what it meets.

Practically, this means leg A should be the person you want to answer first. For click-to-call, that is the agent's own phone. Ringing the customer first and then hunting for a free agent produces exactly the awkward silence that annoys people about automated calls.

From the CLI

Two forms exist. Connect the new channel to a dialplan extension:

channel originate <tech/data> extension [exten@][context]

Or hand it straight to an application:

channel originate <tech/data> application <appname> [appdata]

Concretely:

asterisk -rx "channel originate PJSIP/1001 extension 5551234@from-internal"
asterisk -rx "channel originate PJSIP/1001 application Playback hello-world"

Two details worth knowing. If you omit the context, the default context is used — which is rarely where your routing lives, so specify it explicitly. And calls originated from the CLI are given a timeout of 30 seconds. If the phone rings longer than that, the origination fails. This is the answer to "why does my originate give up while the phone is still ringing": it is a fixed CLI behaviour, not a bug, and it is a reason to use AMI where you need control over the timeout.

From AMI

AMI is the interface for anything programmatic. The Originate action takes these headers:

  • Channel — the channel to call (leg A). Required.
  • Exten, Context, Priority — the dialplan destination for leg B. These three go together; specifying one requires the others.
  • Application and Data — the alternative to Exten/Context/Priority: run an application instead. Data requires Application.
  • Timeout — how long to wait for an answer, in milliseconds.
  • CallerID — the caller ID set on the outgoing channel.
  • Variable — a channel variable to set. Multiple Variable: headers are allowed.
  • EarlyMedia — set true to bridge on early media.
  • Async — set true for fast origination.

A click-to-call request looks like this:

Action: Originate
Channel: PJSIP/1001
Context: from-internal
Exten: 5551234
Priority: 1
CallerID: Sales <5550100>
Timeout: 30000
Async: true
Variable: CRM_TICKET=48210

Set Async: true for anything user-facing. Without it, the AMI connection blocks until the origination completes — so a web request that triggers a call will sit there for the whole ring duration and quite possibly time out. Asynchronous origination returns immediately and reports the outcome later via an OriginateResponse event.

Note the Timeout unit: milliseconds, not seconds. Passing 30 intending half a minute gives you thirty milliseconds and an origination that fails instantly.

The Variable header is how you carry context into the dialplan — a ticket number, a campaign ID, a customer reference — so the receiving extension can behave accordingly and the value can be written into the CDR userfield.

Custom SIP headers

To attach a SIP header to the outgoing call, set it through a variable rather than looking for a dedicated field:

Variable: __SIPADDHEADER51=X-Ticket-Ref: 48210

The double underscore makes the variable inheritable so it survives onto the channel that actually places the call.

Call files

The third method needs no interface at all: write a text file describing the call into Asterisk's outgoing spool directory, and Asterisk places it. It is crude but genuinely useful for scripts and cron jobs.

Channel: PJSIP/1001
Context: from-internal
Extension: 5551234
Priority: 1
CallerID: Reminder <5550100>
MaxRetries: 2
RetryTime: 60
WaitTime: 30

Write the file elsewhere and then mv it into /var/spool/asterisk/outgoing/. Do not create it in place — Asterisk watches that directory and will happily pick up a half-written file. A move is atomic; a write is not.

Call files also give you retries for free via MaxRetries and RetryTime, which AMI does not.

Choosing between them

  • CLI — testing and one-off calls. Fixed 30-second timeout.
  • AMI — applications, click-to-call, anything needing status back. Use Async.
  • Call files — scripts, cron jobs, simple retry logic, no persistent connection.

For richer control — moving a live call, adding it to a bridge, reacting to events mid-call — ARI is the modern interface and worth the extra complexity when you need it.

Frequently asked questions

Why does my originated call fail after 30 seconds?

Calls originated from the CLI carry a fixed 30-second timeout. Use AMI with an explicit Timeout header (in milliseconds) if you need longer.

Why does my web app hang when placing a call?

You are originating synchronously. Set Async: true so AMI returns immediately and reports the result later via OriginateResponse.

Which leg should be the agent?

Leg A — the channel in the Channel header. Ring the agent first, then connect them to the customer, so nobody answers to silence.

How do I pass data into the dialplan?

Use one or more Variable: headers on the Originate action. For values that must survive onto subsequent channels, prefix the variable name with a double underscore.