Back to Blog
voip

voip-with-freeswitch

January 22, 202415 min min read

Building Scalable VoIP with FreeSWITCH

FreeSWITCH is a powerful, open-source telephony platform that can handle millions of concurrent calls. Combined with .NET, you can build enterprise-grade VoIP solutions that scale.

What is FreeSWITCH?

FreeSWITCH is a software-defined telecom stack enabling:

  • Voice calls (SIP, WebRTC)
  • Video calls and conferencing
  • SMS/MMS messaging
  • IVR (Interactive Voice Response)
  • Call recording and transcription

Architecture Overview

┌─────────────┐
│   SIP Clients   │
└────────┬────────┘

    ┌────▼─────┐
    │ FreeSWITCH  │
    └────┬─────┘

    ┌────▼─────┐
    │  .NET API   │
    └────┬─────┘

    ┌────▼─────┐
    │  Database   │
    └──────────┘

FreeSWITCH Installation

Ubuntu/Debian

# Add FreeSWITCH repository
wget -O - https://files.freeswitch.org/repo/deb/debian-release/fsstretch-archive-keyring.asc | apt-key add -</p><h1>Install FreeSWITCH</h1>
apt-get update
apt-get install freeswitch-meta-all

Configuration

Basic vars.xml setup:
<X-PRE-PROCESS cmd="set" data="domain=your-domain.com"/>
<X-PRE-PROCESS cmd="set" data="internal<em>sip</em>port=5060"/>
<X-PRE-PROCESS cmd="set" data="external<em>rtp</em>ip=auto"/>

.NET Integration

ESL (Event Socket Library)

Connect to FreeSWITCH from .NET:

using FreeSwitch.EventSocket;</p><p>public class FreeSwitchService
{
    private readonly InboundSocket <em>socket;
    
    public async Task ConnectAsync()
    {
        </em>socket = new InboundSocket("localhost", 8021, "ClueCon");
        await <em>socket.ConnectAsync();
        
        </em>socket.OnEvent += HandleEvent;
    }
    
    private void HandleEvent(Event fsEvent)
    {
        switch (fsEvent.EventName)
        {
            case "CHANNEL<em>CREATE":
                Console.WriteLine($"New call: {fsEvent.GetHeader("Caller-ANI")}");
                break;
            case "CHANNEL</em>HANGUP":
                Console.WriteLine("Call ended");
                break;
        }
    }
}

Making Outbound Calls

public async Task<string> OriginateCallAsync(string destination, string callerId)
{
    var command = $"originate {{origination<em>caller</em>id<em>number={callerId}}}sofia/gateway/provider/{destination} &park";
    
    var response = await </em>socket.SendApiAsync(new ApiCommand(command));
    
    if (response.Success)
        return response.Data; // Returns UUID
    
    throw new Exception("Call failed: " + response.ErrorMessage);
}

IVR with .NET

public class IvrController : OutboundSocket
{
    public override async Task OnConnectAsync()
    {
        await Answer();
        await Sleep(1000);
        
        var digit = await PlayAndGetDigits(
            minDigits: 1,
            maxDigits: 1,
            tries: 3,
            timeout: 5000,
            terminators: "#",
            soundFile: "ivr/main-menu.wav",
            invalidFile: "ivr/invalid.wav"
        );
        
        switch (digit)
        {
            case "1":
                await Transfer("sales-queue");
                break;
            case "2":
                await Transfer("support-queue");
                break;
            default:
                await Playback("ivr/goodbye.wav");
                await Hangup();
                break;
        }
    }
}

Advanced Features

Call Recording

<!-- In dialplan -->
<action application="record<em>session" data="/recordings/${uuid}.wav"/>
public async Task StartRecordingAsync(string uuid)
{
    await </em>socket.SendApiAsync(
        new ApiCommand($"uuid<em>record {uuid} start /recordings/{uuid}.wav")
    );
}

Conference Rooms

public async Task CreateConferenceAsync(string roomName, string pin)
{
    var command = $"conference {roomName} dial {{conference</em>pin={pin}}}sofia/gateway/provider/conference";
    await <em>socket.SendApiAsync(new ApiCommand(command));
}</p><p>public async Task<ConferenceStatus> GetConferenceStatusAsync(string roomName)
{
    var response = await </em>socket.SendApiAsync(
        new ApiCommand($"conference {roomName} list")
    );
    
    return ParseConferenceResponse(response.Data);
}

WebRTC Support

FreeSWITCH configuration for WebRTC:

<profile name="webrtc">
  <param name="ws-binding" value=":5066"/>
  <param name="wss-binding" value=":7443"/>
  <param name="tls-cert-dir" value="/etc/freeswitch/tls"/>
</profile>

JavaScript client:

const phone = new SIP.UA({
    uri: 'sip:user@domain.com',
    transportOptions: {
        wsServers: ['wss://your-server.com:7443'],
        traceSip: true
    }
});</p><p>phone.on('connected', () => {
    phone.invite('sip:destination@domain.com');
});

Scaling FreeSWITCH

Load Balancing

Use Kamailio or OpenSIPS as SIP proxy:
┌─────────┐
    │  Clients  │
    └────┬────┘

    ┌────▼────┐
    │ Kamailio  │
    └────┬────┘

    ┌────▼────────────────┐
    │  FreeSWITCH Cluster  │
    │  FS1  │  FS2  │ FS3  │
    └─────────────────────┘

Database Backend

Store users in PostgreSQL:
CREATE TABLE users (
    username VARCHAR(255) PRIMARY KEY,
    password VARCHAR(255),
    domain VARCHAR(255),
    enabled BOOLEAN DEFAULT true
);

Configure FreeSWITCH to use database:

<param name="odbc-dsn" value="dsn:username:password"/>

Monitoring & Analytics

Real-time Statistics

public class CallStatistics
{
    public async Task<Stats> GetStatsAsync()
    {
        var channels = await <em>socket.SendApiAsync(new ApiCommand("show channels count"));
        var calls = await </em>socket.SendApiAsync(new ApiCommand("show calls count"));
        
        return new Stats
        {
            ActiveChannels = int.Parse(channels.Data),
            ActiveCalls = int.Parse(calls.Data)
        };
    }
}

CDR (Call Detail Records)

Store CDRs in database for analytics:
public class CdrRecord
{
    public Guid CallId { get; set; }
    public string Caller { get; set; }
    public string Callee { get; set; }
    public DateTime StartTime { get; set; }
    public DateTime? EndTime { get; set; }
    public int Duration { get; set; }
    public string Disposition { get; set; } // ANSWERED, BUSY, NO_ANSWER
}

Best Practices

Use ESL for control: Event Socket is more reliable than XML-RPC ✅ Implement reconnection logic: Handle FreeSWITCH restarts gracefully ✅ Monitor memory usage: FreeSWITCH can be resource-intensive ✅ Use SIP proxies: Don't expose FreeSWITCH directly to internet ✅ Implement rate limiting: Prevent toll fraud ✅ Regular backups: Configuration and recordings

Conclusion

FreeSWITCH combined with .NET provides a powerful stack for building VoIP solutions. Whether you're building a call center, conferencing platform, or SIP gateway, this combination offers the flexibility and scalability needed for enterprise deployments.

The learning curve is steep, but the capabilities are worth the investment. Start with basic call routing, then gradually add features like IVR, recording, and WebRTC support.

Share this article

Tags

FreeSWITCH VoIP SIP .NET

Subscribe to Newsletter

Get notified about new articles