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.

FreeSWITCH ships a single, authoritative C++ ESL implementation in libs/esl/ and uses SWIG to generate language bindings from the interface definition in libs/esl/ESL.i. Every binding exposes the same two classes — ESLconnection and ESLevent — with identical method names (adjusted for each language’s conventions), so code written for one language translates directly to any other. The bindings are available for Python 3, Lua, Ruby, Perl, Java, and .NET/C#, plus the original C++ API for native development.

Connect and Send a Command

The following examples all do the same thing: connect to FreeSWITCH on 127.0.0.1:8021, authenticate with ClueCon, run the status API command, and print the response body.
# libs/esl/python3/single_command.py (adapted for Python 3)
import ESL

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

if con.connected():
    e = con.api('status')
    print(e.getBody())
else:
    print("Not connected")
Install the Python 3 binding by building it in libs/esl/python3/:
cd /usr/src/freeswitch/libs/esl
make pymod
The resulting ESL.py and _ESL.so must be on your PYTHONPATH.

Building the Bindings

All language bindings are generated from the single SWIG interface file libs/esl/ESL.i, which includes esl_oop.h containing the canonical C++ class definitions. SWIG reads this file and emits a language-specific wrapper (esl_wrap.cpp for the C/C++ glue, plus a language module file) that is then compiled and linked against libesl.
# Build all bindings at once
cd /usr/src/freeswitch/libs/esl
make

# Or build individual language modules
make pymod      # Python 3  → ESL.py + _ESL.so
make luamod     # Lua       → ESL.so
make perlmod    # Perl      → ESL.pm + ESL.so
make rubymod    # Ruby      → ESL.rb + ESL.so
make javamod    # Java      → libesljni.so + .class files
make managedmod # .NET/C#   → ESLmanaged.dll
Pre-built packages exist for some distributions. On Debian/Ubuntu, freeswitch-mod-python3 and freeswitch-mod-lua provide the modules as system packages, which is simpler than building from source. Check the SignalWire FreeSWITCH packages for your distribution.

Python 3: Event Loop Example

This pattern is taken directly from libs/esl/python3/events.py. It subscribes to all events and prints each one as it arrives:
#!/usr/bin/env python3
# Adapted from libs/esl/python3/events.py

import ESL

con = ESL.ESLconnection("localhost", "8021", "ClueCon")

if con.connected():
    # Subscribe to every event FreeSWITCH emits
    con.events("plain", "all")

    while True:
        e = con.recvEvent()

        if e:
            # serialize() returns the full event as a plain-text string
            print(e.serialize())
For selective subscriptions combine events() with filter():
import ESL

con = ESL.ESLconnection("localhost", "8021", "ClueCon")

if con.connected():
    # Only CHANNEL_ANSWER, DTMF, and CHANNEL_HANGUP events
    con.events("plain", "CHANNEL_ANSWER DTMF CHANNEL_HANGUP")

    while con.connected():
        e = con.recvEventTimed(500)  # 500 ms timeout
        if not e:
            continue  # timeout — do other work here

        name = e.getHeader("Event-Name")
        uuid = e.getHeader("Unique-ID")

        if name == "CHANNEL_ANSWER":
            print(f"Call answered: {uuid}")
        elif name == "DTMF":
            digit = e.getHeader("DTMF-Digit")
            print(f"DTMF {digit} on call {uuid}")
        elif name == "CHANNEL_HANGUP":
            cause = e.getHeader("Hangup-Cause")
            print(f"Hangup {uuid}: {cause}")

Python 3: Outbound Server

This pattern is taken from libs/esl/python3/server.py. FreeSWITCH connects to this server for each call that hits the socket dialplan application:
#!/usr/bin/env python3
# Adapted from libs/esl/python3/server.py

import socketserver
import ESL

class ESLRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        print(f"{self.client_address} connected!")

        fd  = self.request.fileno()
        con = ESL.ESLconnection(fd)

        print(f"Connected: {con.connected()}")

        if con.connected():
            info = con.getInfo()
            uuid = info.getHeader("unique-id")
            print(f"Call UUID: {uuid}")

            # Answer and play a file
            con.execute("answer",   "",              uuid)
            con.execute("playback", "/ram/swimp.raw", uuid)

# Bind and serve forever — one thread per connection
server = socketserver.ThreadingTCPServer(("", 8040), ESLRequestHandler)
server.serve_forever()

Perl: Subscribing to Events

From libs/esl/perl/events.pl — iterates through every header on each received event:
#!/usr/bin/perl
require ESL;

my $con = new ESL::ESLconnection("localhost", "8021", "ClueCon");
$con->events("plain", "all");

while ($con->connected()) {
    my $e = $con->recvEvent();

    if ($e) {
        my $h = $e->firstHeader();
        while ($h) {
            printf "Header: [%s] = [%s]\n", $h, $e->getHeader($h);
            $h = $e->nextHeader();
        }
    }
}

Ruby: Outbound Server with DTMF

From libs/esl/ruby/server3.rb — a forking outbound server that handles DTMF during playback:
#!/usr/bin/ruby
require "ESL"
require 'socket'
include Socket::Constants

bind_address = "127.0.0.1"
bind_port    = 8086

socket = Socket.new(AF_INET, SOCK_STREAM, 0)
socket.bind(Socket.sockaddr_in(bind_port, bind_address))
socket.listen(5)
puts "Listening on #{bind_address}:#{bind_port}"

loop do
  client_socket, _addr = socket.accept
  pid = fork do
    @con = ESL::ESLconnection.new(client_socket.fileno)
    info = @con.getInfo
    uuid = info.getHeader("UNIQUE-ID")

    @con.sendRecv("myevents")
    @con.sendRecv("divert_events on")

    @con.execute("answer",   "")
    @con.execute("playback", "/usr/local/freeswitch/sounds/music/8000/suite-espanola-op-47-leyenda.wav")

    while @con.connected
      e = @con.recvEvent
      next unless e

      name = e.getHeader("Event-Name")
      break if name == "SERVER_DISCONNECTED"

      if name == "DTMF"
        digit = e.getHeader("DTMF-Digit")
        puts "DTMF: #{digit}"
        @con.execute("transfer", "99355151") if digit == "9"
      end
    end
    puts "Connection closed"
  end
  Process.detach(pid)
end

Java: Full Connect Example

import org.freeswitch.esl.ESLconnection;
import org.freeswitch.esl.ESLevent;

public class ESLDemo {
    public static void main(String[] args) throws Exception {
        ESLconnection con = new ESLconnection("127.0.0.1", "8021", "ClueCon");

        if (con.connected() == 0) {
            System.err.println("Failed to connect");
            return;
        }

        // Subscribe to all events
        con.events("plain", "all");

        // Run a background originate
        ESLevent job = con.bgapi(
            "originate",
            "sofia/internal/1001@pbx.example.com &echo",
            ""
        );
        String jobUuid = job.getHeader("Job-UUID");
        System.out.println("Job UUID: " + jobUuid);

        // Receive events in a loop
        for (int i = 0; i < 10; i++) {
            ESLevent e = con.recvEventTimed(2000);
            if (e != null) {
                System.out.println("Event: " + e.getHeader("Event-Name"));
                String body = e.getBody();
                if (body != null && !body.isEmpty()) {
                    System.out.println("Body: " + body);
                }
            }
        }

        con.disconnect();
    }
}
ESL is not thread-safe at the connection level. Create one ESLconnection per thread — do not share a single connection across multiple threads. If you need to multiplex events across threads, use an inbound connection in a dedicated reader thread and dispatch events to worker threads via a queue.

Build docs developers (and LLMs) love