VoipSDK for .NET · early access

Build VoIP servers and softphones in C#.

One SDK for SIP registration, call control, RTP media, NAT traversal and IVR prompts. Start with a twenty-line client, grow into a registrar with carrier trunks, or ship your own branded softphone.

  • .NET 8, 9 and 10
  • UDP, TCP and TLS signalling
  • Licensed per IP and concurrent calls
Program.cs
using Microsoft.Extensions.DependencyInjection;
using VoipSdk;
using VoipSdk.Audio.Windows;
using VoipSdk.Sip.Sipsorcery;

var sipPassword = Environment.GetEnvironmentVariable("SIP_PASSWORD")!;
var services = new ServiceCollection();
services.AddVoipSdk(new VoipClientOptions
{
    Accounts = [new SipAccountOptions
    {
        Id = "work", Domain = "pbx.example.net",
        Username = "1001", Password = sipPassword
    }]
}).AddSipsorcery().AddWindowsAudio();

await using var provider = services.BuildServiceProvider();
var client = provider.GetRequiredService<IVoipClient>();
client.IncomingCall += async (_, e) => await e.Call.AcceptAsync();

await client.StartAsync();                  // register
var call = await client.DialAsync("1002");  // send INVITE
7codec choices, from Opus and G.722 to G.729 and GSM
100concurrent calls in load testing on a single Windows PC
3NAT modes: off, STUN, or ICE with TURN relay
1async API for clients, servers and softphones
Capabilities

A complete calling stack, not a demo.

Everything a production phone or PBX needs, behind one clear, asynchronous API.

Call control

Answer, decline, busy, hold, mute, DTMF (RFC 4733 and SIP INFO), blind and attended REFER transfers.

Media and codecs

RTP/RTCP with Opus, G.722, G.711 A-law and ยต-law, G.729, GSM and L16, offered in your priority order.

NAT traversal

Opt-in STUN mapping, or ICE with STUN and TURN candidates, including relay-only for locked-down networks.

Secure calling

TLS signalling and SDES-SRTP media encryption, optional or required per deployment.

IVR building blocks

An audio cache bank decodes each prompt once and shares it across every call, with looping and barge-in.

Call quality

Live codec, jitter, packet loss and round-trip time per call, plus a simple quality rating.

Developer experience

Clean C#, from your first call to your first server.

An async API with typed events and per-call commands that fits naturally into dependency injection. Swap adapters for audio and history without touching your call logic.

  • Dependency injection first: AddVoipSdk().AddSipsorcery().AddWindowsAudio()
  • Pluggable ports: bring your own audio device or call-history store.
  • Documented public API: XML docs throughout, guarded by an API compatibility test.
  • Testable: an embeddable SIP server for real end-to-end calls in your test suite.
Client.cs
using Microsoft.Extensions.DependencyInjection;
using VoipSdk;
using VoipSdk.Audio.Windows;
using VoipSdk.Sip.Sipsorcery;

var sipPassword = Environment.GetEnvironmentVariable("SIP_PASSWORD")!;
var services = new ServiceCollection();
services.AddVoipSdk(new VoipClientOptions
{
    Accounts = [new SipAccountOptions
    {
        Id = "work", Domain = "pbx.example.net",
        Username = "1001", Password = sipPassword
    }]
}).AddSipsorcery().AddWindowsAudio();

await using var provider = services.BuildServiceProvider();
var client = provider.GetRequiredService<IVoipClient>();
client.IncomingCall += async (_, e) => await e.Call.AcceptAsync();

await client.StartAsync();                  // register
var call = await client.DialAsync("1002");  // send INVITE
Ivr.cs
// Decoded once, shared by every call, encoded once per codec.
await client.AudioCache.PreloadAsync(
    Directory.EnumerateFiles("prompts", "*.wav"));

client.IncomingCall += async (_, e) =>
{
    var call = e.Call;
    await call.AcceptAsync();
    var result = await call.PlayAsync("prompts/welcome.wav",
        new PlaybackOptions { StopOnDtmf = true }); // barge-in

    if (result.Outcome == PlaybackOutcome.Dtmf && result.Digit == '1')
        await call.PlayAsync("prompts/hold-music.wav",
            new PlaybackOptions { Loop = true });
};
Server.cs
using VoipSdk.Server;

await using var server = new VoipSipServer(new SipServerOptions
{
    BindAddress = "0.0.0.0", Port = 5060,
    Domain = "pbx.example.net",
    Accounts =
    [
        new() { Username = "101", Password = Secret("EXT_101") },
        new() { Username = "102", Password = Secret("EXT_102") }
    ],
    Trunks =
    [
        new SipTrunkOptions
        {
            Id = "carrier", Name = "Carrier",
            ServerAddress = "sip.carrier.example",
            Username = "trunk-user", Password = Secret("TRUNK"),
            IncomingExtension = "101", OutboundPrefix = "9"
        }
    ]
});

await server.StartAsync();
Console.WriteLine($"Listening on {server.BoundEndpoint}");

static string Secret(string name) =>
    Environment.GetEnvironmentVariable(name)!;
MainWindow.xaml.cs
using VoipSdk.Softphone;
using VoipSdk.Softphone.ViewModels;

Action<Action> ui = action =>
    dispatcherQueue.TryEnqueue(() => action());

var engine = new SoftphoneEngine(new SoftphoneEngineOptions
{
    Dispatch = ui,   // state changes arrive on your UI thread
    Branding = SoftphoneBranding.LoadFrom(AppContext.BaseDirectory)
});
var phone = new PhoneViewModel(engine, ui);
await phone.InitializeAsync();

Dialer.ViewModel = phone;   // the WinUI dialler control
await phone.OpenLinkAsync("tel:+27315550100");
Architecture

Your code never touches a SIP library.

Your application talks to one public API. SIP, audio devices and storage sit behind adapters, so each can change without breaking you.

The core is UI-free and works in services, desktop apps and tests. Windows audio (WASAPI) and SQLite call history are included; implement the audio or history port to run elsewhere.

Licensing

Pay for what you run: IP addresses and concurrent calls.

A VoipSDK licence covers the servers your calls run on and the number of calls you carry at once. Tell us your footprint and we will send you a quote.