Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/signalwire/freeswitch/llms.txt

Use this file to discover all available pages before exploring further.

In inbound mode, your application is the client and FreeSWITCH is the server. Your code opens a TCP connection to FreeSWITCH’s Event Socket port (default 8021), authenticates with the configured password, and then has full access to FreeSWITCH’s API surface — sending commands, subscribing to events for any active call, originating new calls, and manipulating existing sessions. This is the most common ESL usage pattern: a single long-lived connection from a management process that oversees the entire FreeSWITCH instance.

Configuration

ESL is configured in conf/autoload_configs/event_socket.conf.xml. The relevant parameters for inbound mode are:
<configuration name="event_socket.conf" description="Socket Client">
  <settings>
    <param name="nat-map"     value="false"/>
    <param name="listen-ip"   value="::"/>
    <param name="listen-port" value="8021"/>
    <param name="password"    value="ClueCon"/>
    <!--<param name="apply-inbound-acl" value="loopback.auto"/>-->
    <!--<param name="stop-on-bind-error" value="true"/>-->
  </settings>
</configuration>
ParameterDefaultDescription
listen-ip::IP address to bind on. :: listens on all interfaces (IPv4 + IPv6). Use 127.0.0.1 to restrict to loopback only.
listen-port8021TCP port ESL listens on
passwordClueConAuthentication password. Change this in production.
apply-inbound-acl(disabled)Name of an ACL (from acl.conf.xml) to restrict connecting IPs
stop-on-bind-error(disabled)If true, FreeSWITCH will refuse to start when the ESL port cannot be bound
After editing the config, reload it without restarting:
fs_cli -x "reloadxml"
fs_cli -x "reload mod_event_socket"

Connecting

Use the ESLconnection constructor with (host, port, password). The constructor blocks until authentication completes. Check connected() before sending any commands.
import ESL

con = ESL.ESLconnection('127.0.0.1', '8021', 'ClueCon')

if con.connected():
    print("Connected to FreeSWITCH")
else:
    print("Connection failed")
You can also test connectivity with a raw telnet session to verify the server is reachable and the password is correct before writing code:
telnet 127.0.0.1 8021
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
Content-Type: auth/request

auth ClueCon

Content-Type: command/reply
Reply-Text: +OK accepted

api status

Content-Type: api/response
Content-Length: 182

UP 0 years, 0 days, 1 hours, 23 minutes, 45 seconds, 123 milliseconds, 456 microseconds
FreeSWITCH (Version 1.10.x) is ready
...

Sending API Commands

ESL exposes two API execution methods depending on whether you need the result immediately.

Synchronous: api()

api() sends the command and blocks until FreeSWITCH returns the response. Use it for quick administrative queries where you need the answer before proceeding.
import ESL

con = ESL.ESLconnection('127.0.0.1', '8021', 'ClueCon')

if con.connected():
    # Synchronous: blocks until FreeSWITCH replies
    e = con.api('status')
    print(e.getBody())

    # With arguments
    e = con.api('sofia', 'status')
    print(e.getBody())

Background: bgapi()

bgapi() returns immediately and executes the command in a FreeSWITCH background thread. The result arrives later as a BACKGROUND_JOB event containing a Job-UUID that matches what bgapi() returned.
import ESL

con = ESL.ESLconnection('127.0.0.1', '8021', 'ClueCon')

if con.connected():
    con.events('plain', 'BACKGROUND_JOB')

    # Fire and forget — result comes back as an event
    e = con.bgapi('originate', 'sofia/internal/1001@pbx.example.com &echo')
    job_uuid = e.getHeader('Job-UUID')
    print(f"Job dispatched: {job_uuid}")

    # Collect the result event
    result = con.recvEvent()
    if result and result.getHeader('Job-UUID') == job_uuid:
        print(result.getBody())
Use bgapi() for any command that takes more than a few milliseconds — especially originate, bridge, and calls to external services. Blocking the ESL connection with a long-running api() call prevents your process from receiving other events while it waits.

Subscribing to Events

After connecting, subscribe to event streams using events(). FreeSWITCH will push matching events to your connection as they occur.

Subscribe to all events

con.events('plain', 'all')

Subscribe to specific events only

# Only receive CHANNEL_ANSWER and DTMF events
con.events('plain', 'CHANNEL_ANSWER DTMF')

Filter events by header value

filter() narrows delivery further — only events where the named header matches the given value will be sent:
# Only events for this specific call UUID
con.filter('Unique-ID', 'a1b2c3d4-e5f6-7890-abcd-ef1234567890')

Event loop example

import ESL

con = ESL.ESLconnection('127.0.0.1', '8021', 'ClueCon')

if con.connected():
    con.events('plain', 'all')

    while con.connected():
        # Block until an event arrives
        e = con.recvEvent()

        if e:
            name = e.getHeader('Event-Name')
            uuid = e.getHeader('Unique-ID')
            print(f"Event: {name}  UUID: {uuid}")

            if name == 'CHANNEL_HANGUP':
                cause = e.getHeader('Hangup-Cause')
                print(f"  → Call {uuid} hung up: {cause}")

Using recvEventTimed() for a non-blocking loop

import ESL
import time

con = ESL.ESLconnection('127.0.0.1', '8021', 'ClueCon')

if con.connected():
    con.events('plain', 'CHANNEL_ANSWER CHANNEL_HANGUP DTMF')

    while con.connected():
        # Wait up to 500 ms for an event, then do other work
        e = con.recvEventTimed(500)

        if e:
            print(f"Event: {e.getHeader('Event-Name')}")
        else:
            # No event within the timeout — do housekeeping here
            pass

Originating Calls

Use api('originate', ...) or bgapi('originate', ...) to place outbound calls. The originate command syntax is:
originate <call-url> <exten>|&<app>(args) [<dialplan>] [<context>] [<cid_name>] [<cid_num>] [<timeout>]

Examples

import ESL

con = ESL.ESLconnection('127.0.0.1', '8021', 'ClueCon')

if con.connected():
    # Call a SIP extension and send it to dialplan extension 1000 in default context
    e = con.bgapi('originate',
        'sofia/internal/1001@pbx.example.com 1000 XML default '
        '"Test Call" 5551234567 30'
    )
    print(f"Originate job: {e.getHeader('Job-UUID')}")

    # Call and immediately connect to an application instead of dialplan
    e = con.bgapi('originate',
        'sofia/internal/1001@pbx.example.com &playback(/usr/local/freeswitch/sounds/en/us/callie/ivr/ivr-welcome.wav)'
    )
    print(f"Playback job: {e.getHeader('Job-UUID')}")

Controlling Active Calls

Once a call is active you can manipulate it using ESL API commands that target its UUID. Retrieve the UUID from incoming events (the Unique-ID header) or from the originate BACKGROUND_JOB response body.
import ESL

con = ESL.ESLconnection('127.0.0.1', '8021', 'ClueCon')

if con.connected():
    uuid = 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'

    # Hang up a call
    e = con.api('uuid_kill', uuid)
    print(e.getBody())

    # Bridge two calls together
    other_uuid = 'b2c3d4e5-f6a7-8901-bcde-f01234567891'
    e = con.api('uuid_bridge', f'{uuid} {other_uuid}')
    print(e.getBody())

    # Transfer a call to a new dialplan destination
    e = con.api('uuid_transfer', f'{uuid} 2000 XML default')
    print(e.getBody())

    # Answer a ringing call leg
    e = con.api('uuid_answer', uuid)
    print(e.getBody())

    # Play a file to one leg of a call
    e = con.api('uuid_broadcast',
        f'{uuid} /usr/local/freeswitch/sounds/en/us/callie/ivr/ivr-hold_connect_call.wav aleg'
    )
    print(e.getBody())

Common UUID commands

CommandSyntaxDescription
uuid_killuuid_kill <uuid> [cause]Hang up the call with an optional hangup cause
uuid_bridgeuuid_bridge <uuid1> <uuid2>Bridge two existing call legs together
uuid_transferuuid_transfer <uuid> <dest> [<dialplan>] [<context>]Transfer the call to a new dialplan destination
uuid_answeruuid_answer <uuid>Answer a ringing inbound call
uuid_broadcastuuid_broadcast <uuid> <path> [aleg|bleg|both]Play audio to a call leg
uuid_holduuid_hold <uuid>Place a call on hold
uuid_parkuuid_park <uuid>Park a call
uuid_getvaruuid_getvar <uuid> <var>Read a channel variable
uuid_setvaruuid_setvar <uuid> <var> <value>Set a channel variable

Build docs developers (and LLMs) love