voip-with-freeswitch
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
Basicvars.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.
Tags
Subscribe to Newsletter
Get notified about new articles