VoIP servers and IVRs

Your own SIP server, written in C#.

Embed a registrar and call router in any .NET 10 host. Connect carrier trunks, answer with IVR prompts and control everything from your existing systems over HTTP.

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)!;
Registrar and routing

Phones register. Calls connect. Carriers answer.

Authenticated registrar

SIP digest authentication with expiring single-contact bindings, nonce lifetimes and configurable registration limits.

Call routing

Route calls between registered extensions, with dialog limits and automatic cleanup of abandoned transactions.

Carrier trunks

Register to SIP providers, deliver inbound calls to an extension and send outbound calls with a dial prefix.

HTTP control API

Provision extensions, watch registrations and calls over live server-sent events, route calls and place click-to-call from your main system.

Built-in audio test

A reserved extension plays a looping recording, so installers and support teams can prove two-way audio in seconds.

Per-leg quality

Jitter, loss and round-trip time for every leg, plus process CPU, memory and network metrics.

IVR

Hundreds of prompts. Thousands of callers. One copy in memory.

IVRs replay the same audio constantly. The audio cache bank loads each prompt once and shares it with every call.

  • Decoded once into shared 48 kHz audio; concurrent requests share one load.
  • Encoded once per codec and reused by every later call.
  • Memory budget with least-recently-used eviction; playing prompts are never evicted.
  • Barge-in: a key press stops the prompt and returns the digit.
  • Broad WAV support: PCM, float, A-law and ยต-law, 8 to 192 kHz, up to 8 channels.
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 });
};
Load tested

Measure capacity before your customers do.

The included load generator drives pairs of real SDK phones through your server and records quality and resource use as it goes.

In our own load testing, a single Windows PC carried 100 concurrent calls without fault. Run the same tool against your hardware to size your licence with real numbers.

What a load test records

  • Active calls, registrations and ramp progress
  • Media bytes sent and received per second
  • Worst-case jitter, packet loss and round-trip time
  • Server and generator CPU and memory
  • CSV export for your capacity plan

Planning a hosted PBX or IVR platform?

Licensing scales with the IP addresses you deploy on and your peak concurrent calls.