StreamCoreAI SIP Server

August 26, 2026 · View on GitHub

English | 简体中文

A SIP server that bridges SIP phone calls to the StreamCoreAI server. It handles incoming calls (e.g., from a phone network) and outgoing calls (initiated via REST API), converting audio between G.711 μ-law (PCMU) used by SIP and Opus used by StreamCoreAI's WebRTC transport.

For splitting this tree into its own Git repository and publishing github.com/streamcoreai/go-sdk, see Repository layout.

Architecture

Phone Network ←→ SIP (PCMU/RTP) ←→ SIP Server ←→ WHIP (Opus/WebRTC) ←→ StreamCoreAI Server

Each call creates:

  • A local RTP listener for SIP-side audio (PCMU at 8kHz)
  • A StreamCoreAI SDK client connection via WHIP (Opus at 48kHz)
  • A bidirectional audio bridge that transcodes and resamples between the two

Prerequisites

  • Go 1.23+
  • A running StreamCoreAI server with a WHIP endpoint

Configuration

Copy and edit the config file:

cp config.toml.example config.toml

Key settings in config.toml:

[sip]
listen_addr = "0.0.0.0:5060"   # SIP UDP listen address
external_ip = ""                 # Public IP for SDP (auto-detected if empty)
rtp_port_min = 10000
rtp_port_max = 20000

[streamcoreai]
whip_endpoint = "http://localhost:8080/whip"
token_url = "http://localhost:8080/token"
api_key = "sk-streamcore-demo-key"

[api]
listen_addr = "0.0.0.0:8090"

[providers.twilio]
enabled = false
trunk_domain = "your-trunk.pstn.twilio.com"

[providers.ringcentral]
enabled = false
outbound_proxy = "sip.ringcentral.com:5060"

JWT Authentication

When the StreamCoreAI server has jwt_secret set, the SIP server needs to fetch a JWT to authenticate WHIP requests. Add token_url and api_key under [streamcoreai]:

[streamcoreai]
whip_endpoint = "http://localhost:8080/whip"
token_url = "http://localhost:8080/token"
api_key = "sk-streamcore-demo-key"

Caller Identity

Every call forwards the far end's number to StreamCoreAI as its resource_id — the caller on an inbound call, the callee on an outbound one. An external agent (bring your own agent) receives it alongside each turn and can use it to scope memory to the person, so a repeat caller is recognised instead of starting over.

No configuration is required. With token_url set, the number is signed into the JWT as a claim; without it, the SIP server sends it as an X-StreamCore-Resource-Id header, which the StreamCoreAI server accepts from trusted server-side callers like this one.

Running

go build -o sip-server .
./sip-server                    # uses config.toml in current directory
./sip-server /path/to/config.toml

REST API

Health Check

GET /health

List Providers

GET /providers

List Calls

GET /calls

Get Call

GET /calls/{id}

Initiate Outgoing Call

POST /calls
Content-Type: application/json

{
  "to": "+15551234567",
  "provider": "twilio"
}

Hang Up Call

DELETE /calls/{id}

Providers

Asterisk (Local Testing)

A local Asterisk PBX is included for end-to-end testing — see Local Testing with Asterisk below.

Twilio

Configure your Twilio Elastic SIP Trunk domain. Point the trunk's origination URI to your SIP server's address.

RingCentral

Configure the RingCentral outbound proxy and credentials.


Local Testing with Asterisk

A Dockerized Asterisk PBX is included so you can test the full call flow locally without any external SIP providers.

Prerequisites

  • Docker & Docker Compose
  • A running StreamCoreAI server on localhost:8080 (or via the root docker-compose.yml)
  • A SIP softphone app (e.g. Oher, Zoiper, Oher Mobile, Linphone)

Architecture

Softphone (6001)                          StreamCoreAI Server
     │                                          │
     │  SIP/RTP (PCMU)                          │ WHIP (Opus/WebRTC)
     ▼                                          ▼
┌──────────┐    SIP/RTP (PCMU)    ┌──────────────────┐
│ Asterisk │◄────────────────────►│   SIP Server     │
│  (PBX)   │                     │  (this project)  │
└──────────┘                     └──────────────────┘
 172.20.0.10                       172.20.0.20

Quick Start

1. Start Asterisk + SIP Server:

cd sip-server
docker compose up --build

This starts:

  • Asterisk on 172.20.0.10:5060 (mapped to host localhost:5060/udp)
  • SIP Server on 172.20.0.20:5060 (mapped to host localhost:5061/udp) with REST API on localhost:8090

Make sure your StreamCoreAI server is running on localhost:8080 before starting.

2. Configure your softphone:

Register with the Asterisk server using these credentials:

SettingValue
SIP Serverlocalhost (or your machine IP)
SIP Port5060
Username6001
Passwordtest123
TransportUDP

A second extension 6002 / test123 is also available.

3. Test an incoming call (phone → StreamCoreAI):

From your softphone, dial 7000. Asterisk routes the call to the SIP server, which connects to StreamCoreAI. You should hear the AI agent respond.

4. Test an outgoing call (StreamCoreAI → phone):

Use the REST API to initiate a call from StreamCoreAI to your softphone:

curl -X POST http://localhost:8090/calls \
  -H "Content-Type: application/json" \
  -d '{"to": "6001", "provider": "asterisk"}'

Your softphone should ring. Answer it to talk to the AI agent.

5. Manage calls:

# List active calls
curl http://localhost:8090/calls

# Get call details
curl http://localhost:8090/calls/<call-id>

# Hang up a call
curl -X DELETE http://localhost:8090/calls/<call-id>

# Check health
curl http://localhost:8090/health

# List providers
curl http://localhost:8090/providers

Running Without Docker

If you want to run just Asterisk in Docker and the SIP server natively:

# Start only Asterisk
docker compose up asterisk --build

# Run SIP server locally (uses config.toml which points to localhost)
go build -o sip-server .
./sip-server

The default config.toml has the asterisk provider pointing to localhost:5060.

Asterisk Extensions

ExtensionDescription
7000Routes to the StreamCoreAI SIP server
6001Test softphone extension 1
6002Test softphone extension 2
9999Echo test (plays back your audio for debugging)

Troubleshooting

  • No audio: Make sure the RTP port ranges don't conflict. Asterisk uses 10000-10100, SIP server uses 10200-10300 in Docker mode.
  • Registration failed: Check that Asterisk is running (docker compose logs asterisk) and the credentials match.
  • "provider not configured": Make sure providers.asterisk.enabled = true in your config.
  • StreamCoreAI connection fails: Verify StreamCoreAI is running at the configured WHIP endpoint. In Docker, it uses host.docker.internal:8080.
  • The agent interrupts itself: a reply gets cut mid-sentence, nothing arrives to replace it, and the call sits in dead air. There is no AEC anywhere on a carrier path, so the agent's own voice comes back attenuated but structurally identical to speech and the energy VAD calls it a barge-in. This server sends aec=none on every call, and StreamCoreAI's default pipeline.echo_guard = "auto" turns on an echo bound for exactly those sessions while leaving browser sessions on the same instance alone. If you still see it, check the server log for echo guard on at call setup, and confirm echo_guard has not been set to "off". Raising the VAD threshold instead only trades this for being deaf to quiet callers.
  • Inbound goes silent while the agent speaks: this looks like half-duplex media and sends people hunting through the RTP reader, the transcode, and the caller's handset. The usual cause is DTX — G.729 Annex B or Opus DTX suppressing "silent" RTP, which on a call with an agent talking means all inbound during playback. Force G.711 (PCMU/PCMA) end to end. This server already advertises PCMU only; check the PBX and the trunk for the other legs. No amount of VAD tuning helps when the frames are never sent.

How It Works

Incoming Calls

  1. SIP INVITE arrives → server sends 100 Trying
  2. Parses remote SDP for RTP address
  3. Creates a StreamCoreAI SDK connection via WHIP
  4. Allocates a local RTP port and responds with 200 OK + SDP
  5. Starts bidirectional audio bridge (PCMU ↔ Opus)

Outgoing Calls

  1. REST API request triggers POST /calls
  2. Creates a StreamCoreAI SDK connection
  3. Sends SIP INVITE to the provider's trunk with local SDP
  4. On 200 OK, sends ACK and starts audio bridge

Audio Bridge

  • SIP → StreamCoreAI: Decode G.711 μ-law → upsample 8kHz to 48kHz → encode Opus → send via WebRTC
  • StreamCoreAI → SIP: Decode Opus → downsample 48kHz to 8kHz → encode G.711 μ-law → send via RTP