Last updated August 14, 2026
VoiceBlender bridges SIP and WebRTC voice calls with multi-party audio mixing, real-time speech-to-text, text-to-speech, AI agent integration, recording, and webhook-based event delivery.
Configuration
| Variable | Default | Description |
|---|---|---|
INSTANCE_ID | (auto-generated UUID) | Instance identifier included in all API responses and webhook events |
HTTP_ADDR | :8080 | REST API listen address |
ALLOWED_IPS | (empty = allow all) | Comma-separated allowlist of IPs and CIDR ranges (IPv4 and IPv6) gating every HTTP endpoint, including /v1/vsi, /v1/legs/websocket, /v1/legs/moq, /metrics, and pprof. Bare addresses become /32 (v4) or /128 (v6); malformed entries fail server startup. Only X-Forwarded-For is consulted as a proxy header (see TRUST_PROXY_HEADERS). |
TRUST_PROXY_HEADERS | false | When true, the client IP used for the ALLOWED_IPS check is taken from the leftmost X-Forwarded-For entry (falling back to the socket peer if absent). Enable only behind a trusted reverse proxy that overwrites the header — otherwise it is client-spoofable. |
SIP_BIND_IP | 127.0.0.1 | IPv4 address advertised in SDP, Contact, and Via headers (and used as the listen address when SIP_LISTEN_IP is empty) |
SIP_LISTEN_IP | (same as SIP_BIND_IP) | UDP socket bind IP. Accepts 127.0.0.1, 0.0.0.0, ::, or any literal v4/v6 address |
SIP_BIND_IPV6 | (empty = v4-only) | IPv6 address advertised in SDP/Contact/Via for IPv6 calls. Set this for IPv6-only or dual-stack deployments |
SIP_LISTEN_IPV6 | (same as SIP_BIND_IPV6) | Optional separate IPv6 socket bind address (used when running with both 0.0.0.0 and a specific v6 literal) |
SIP_EXTERNAL_IP | — | Public IPv4 address for NAT/Docker deployments. When set, used in SIP Contact headers and SDP media (c=) lines instead of the bind IP. IPv6 has no equivalent — set SIP_BIND_IPV6 to the address you want advertised. |
SIP_PORT | 5060 | SIP listen port |
SIP_TLS_PORT | (disabled) | SIP-over-TLS listen port (typically 5061). When set, SIP_TLS_CERT and SIP_TLS_KEY must also be provided. Required for WhatsApp Business Calling integration. |
SIP_TLS_CERT | — | Path to PEM-encoded TLS certificate (e.g. fullchain.pem). Meta rejects self-signed certs — use a CA-signed cert matching a public FQDN. |
SIP_TLS_KEY | — | Path to PEM-encoded TLS private key (e.g. privkey.pem). |
SIP_DEBUG | false | When true, log the full RFC 3261 wire form of every inbound and outbound SIP request and response. Very verbose — use only for troubleshooting. |
SIP_DOMAIN | (falls back to advertised IP) | FQDN advertised in From, Contact and Via on all outbound SIP signalling (classic trunks and WhatsApp). Should match the SAN on SIP_TLS_CERT and any allowlist your carrier or Meta keeps. |
SIP_HOST | voiceblender | SIP User-Agent name |
SIP_CODECS | PCMU,PCMA | Comma-separated, preference-ordered list of codecs the SIP engine offers on outbound INVITEs and accepts on inbound INVITEs. Recognized names (case-insensitive): PCMU, PCMA, G722, opus, AMR-WB, AMR-NB (bare token AMR resolves to AMR-NB per RFC 4867 §8.1). Unknown names and duplicates are dropped silently. |
SIP_AUTO_RINGING | false | When true, the server sends 180 Ringing automatically after 100 Trying. Default sends only 100 Trying; the API caller drives ringing via /ring, /early-media, or /answer. |
SIP_USE_SOURCE_SOCKET | false | When true, route SIP responses and in-dialog requests (BYE, re-INVITE, UPDATE, INFO, NOTIFY, REFER) back to the request's source UDP socket instead of the peer's Contact / Via sent-by. Enable when peers advertise unroutable addresses (e.g. private IPs in Contact from behind NAT). |
SIP_REGISTRATION_DEFAULT_EXPIRES_SECONDS | 3600 | Expiry used when an inbound REGISTER carries no Expires value. |
SIP_REGISTRATION_MAX_EXPIRES_SECONDS | 7200 | Upper clamp on the granted REGISTER expiry. Requests above this value are honored at this maximum. |
SIP_REGISTRATION_SWEEP_INTERVAL_MS | 1000 | Sweeper period (ms) for evicting expired AOR bindings. |
SIP_REGISTRATION_ALLOW_MULTIPLE_CONTACTS | true | When true, the same AOR may be bound from multiple Contacts simultaneously (and POST /v1/legs parallel-forks to every bound contact). When false, each REGISTER replaces any prior Contacts for the AOR. |
ICE_SERVERS | stun:stun.l.google.com:19302 | STUN/TURN URLs for WebRTC ICE, comma-separated |
WEBRTC_EXTERNAL_IPS | (empty) | Comma-separated public IPs advertised as host ICE candidates (pion SetNAT1To1IPs). Required when VB runs behind NAT/Docker so peers behind firewalls can reach it; supports IPv4 and IPv6 literals. The literal value "auto" triggers STUN-based public-IP discovery at startup using the configured ICE_SERVERS; failure is non-fatal. |
RTP_PORT_MIN | 10000 | Minimum UDP port for RTP/RTCP media |
RTP_PORT_MAX | 20000 | Maximum UDP port for RTP/RTCP media |
DEFAULT_SAMPLE_RATE | 16000 | Default mixer sample rate (Hz) for new rooms when sample_rate is not specified. Allowed: 8000, 16000, 48000. |
SPEECH_DETECTION_ENABLED | false | Emit speaking.started / speaking.stopped events for every connected leg by default. Per-call speech_detection on POST /v1/legs or POST /v1/legs/{id}/answer overrides this. |
VSI_EVENT_BUFFER_SIZE | 256 | Per-client buffer (in events) on the /v1/vsi WebSocket. When the client falls behind, new events are dropped and the next delivered event carries an events_dropped notification. Clamped to [16, 1000000]. Memory: ~1 KB × buffer per connection at the default. |
AMRWB_MODE | 2 | AMR-WB (G.722.2) encoder speech-mode ceiling 0..8: 0=6.60, 1=8.85, 2=12.65, 3=14.25, 4=15.85, 5=18.25, 6=19.85, 7=23.05, 8=23.85 kbit/s. The actual transmit mode is this ceiling clamped to the peer's negotiated mode-set. Default 2 matches GSMA IR.92 / VoLTE. |
AMRWB_OCTET_ALIGNED | true | Offer octet-aligned AMR-WB framing (RFC 4867) in outbound SDP. When false, offers bandwidth-efficient framing. On answers, VoiceBlender always echoes the framing the peer negotiated. |
AMRNB_MODE | 7 | AMR-NB (RFC 4867) encoder speech-mode ceiling 0..7: 0=4.75, 1=5.15, 2=5.90, 3=6.70, 4=7.40, 5=7.95, 6=10.2, 7=12.2 kbit/s. The actual transmit mode is this ceiling clamped to the peer's negotiated mode-set. Default 7 is GSM-EFR-equivalent 12.2 kbit/s. |
AMRNB_OCTET_ALIGNED | true | Offer octet-aligned AMR-NB framing (RFC 4867) in outbound SDP. When false, offers bandwidth-efficient framing. On answers, VoiceBlender always echoes the framing the peer negotiated. |
RECORDING_DIR | /tmp/recordings | Local directory for recording output files |
LOG_LEVEL | info | Log verbosity: debug, info, warn, error. Transcript text, DTMF digits and event payloads are logged only at debug. |
WEBHOOK_URL | — | Global webhook URL for event delivery (fallback when no per-leg or per-room webhook is set) |
WEBHOOK_SECRET | — | HMAC-SHA256 signing secret for the global webhook |
ELEVENLABS_API_KEY | — | API key for ElevenLabs TTS, STT, and Agent provider |
VAPI_API_KEY | — | API key for VAPI Agent provider |
DEEPGRAM_API_KEY | — | API key for Deepgram STT and TTS |
AZURE_SPEECH_KEY | — | Subscription key for Azure Cognitive Speech Services (TTS and STT) |
AZURE_SPEECH_REGION | eastus | Azure region for Speech Services (e.g. eastus, westeurope) |
S3_BUCKET | — | S3 bucket name for recording uploads |
S3_REGION | us-east-1 | AWS region for S3 |
S3_ENDPOINT | — | Custom S3-compatible endpoint (e.g. MinIO) |
S3_PREFIX | — | Key prefix applied to all S3 objects |
S3_ALLOW_INSECURE_ENDPOINT | false | Allow a plaintext http:// S3_ENDPOINT on a non-local host, rather than refusing to ship recording audio in cleartext (startup: exit 1; per request: 400). Loopback, private and link-local addresses, single-label hostnames and .internal/.local names are exempt and need no opt-in. |
S3_PREFLIGHT_TIMEOUT | 10s | Budget for the HeadBucket probe run at startup. A bucket the store reports absent exits 1; any other probe failure (403 without s3:ListBucket, 5xx, unreachable) is warned about and startup continues. 0 disables the probe. |
S3_REQUEST_PREFLIGHT_TIMEOUT | 2s | Budget for the HeadBucket probe run when a request supplies s3_bucket. A bucket the store reports absent returns 400; any other probe failure is warned about and the recording proceeds. Kept short because it runs inside record-start, which on VSI occupies the connection's command loop. 0 disables the probe. |
GCS_BUCKET | — | Google Cloud Storage bucket for recording uploads via the native GCS API (storage=gcs). Uses Application Default Credentials / Workload Identity — preferred over S3_ENDPOINT=https://storage.googleapis.com on GKE. |
GCS_OBJECT_NAME_PREFIX | — | Object name prefix applied to all GCS uploads (e.g. recordings or a bare workspace id). A trailing slash is added automatically when missing. |
AWS_ACCESS_KEY_ID | — | [SDK-resolved, not read by VoiceBlender] AWS access key for S3 uploads and AWS Polly TTS. Consumed by the AWS SDK default credential chain alongside AWS_SECRET_ACCESS_KEY and the optional AWS_SESSION_TOKEN. |
AWS_SECRET_ACCESS_KEY | — | [SDK-resolved, not read by VoiceBlender] AWS secret key paired with AWS_ACCESS_KEY_ID. |
AWS_SESSION_TOKEN | — | [SDK-resolved, not read by VoiceBlender] Optional temporary-credential session token (STS / SSO) used together with AWS_ACCESS_KEY_ID/SECRET. |
AWS_PROFILE | — | [SDK-resolved, not read by VoiceBlender] Profile name in ~/.aws/credentials to use instead of static AWS_* env vars. |
AWS_REGION | — | [SDK-resolved, not read by VoiceBlender] AWS region used by S3 and Polly when S3_REGION is empty. |
GOOGLE_APPLICATION_CREDENTIALS | — | [SDK-resolved, not read by VoiceBlender] Path to a Google Cloud service-account JSON file used by Google Cloud TTS and by GCS recording uploads when no other credential source is available. Consumed by Google's Application Default Credentials chain (env var → gcloud ADC → GCE/GKE metadata / Workload Identity). |
TTS_CACHE_ENABLED | false | Enable disk-backed TTS audio cache; cached audio persists across restarts |
TTS_CACHE_DIR | /tmp/tts_cache | Directory for cached TTS audio files (used when TTS_CACHE_ENABLED=true) |
TTS_CACHE_INCLUDE_API_KEY | false | Include API key in TTS cache key; set true if different keys map to different voice clones |
SIP_JITTER_BUFFER_MS | 0 | SIP ingress jitter buffer target delay in ms (0 = disabled passthrough). Applies to every SIP leg. |
SIP_JITTER_BUFFER_MAX_MS | 300 | Maximum depth of the SIP ingress jitter buffer in ms. Frames beyond this are dropped oldest-first to catch up after a stall. |
SIP_SDP_STRICT_MLINE_ANSWER | false | Emit a port-0 placeholder for every offered m= section we do not accept, so answers carry the same m-line count and order as the offer (RFC 3264 §6). Gated separately from multi-stream because it changes the SDP single-stream calls emit whenever a peer offers a section we don't handle, such as video. |
SIP_REFER_AUTO_DIAL | false | When true, accept incoming SIP REFER requests and automatically originate the transferred call. Default-deny: stays off unless the SIP edge is locked down (IP allow-lists, digest auth) because auto-dialing arbitrary Refer-To URIs is a classic toll-fraud vector. Outbound transfers initiated via the REST API are unaffected by this flag. |
SIP_TCP_ENABLED | false | Listen for SIP over TCP on SIP_PORT alongside the UDP listener. Recommended with SIPREC: a recording session's INVITE carries the metadata document alongside the SDP and is larger than RFC 3261 section 18.1.1 allows over UDP. |
SIPREC_ENABLED | false | Accept inbound SIPREC recording sessions (RFC 7866), where an SBC or PBX forks a call's media to this server. Off by default: when off, an INVITE carrying Require: siprec is rejected with 420 Bad Extension and one that only hints at SIPREC with 488. |
SIPREC_AUTO_ANSWER | true | Answer an inbound recording session immediately instead of parking it until POST /v1/legs/{id}/answer. A session recording client does not wait for an application decision, so leaving this on is usually correct; turn it off to gate sessions from a controller. |
SIPREC_MAX_STREAMS | 8 | Maximum number of m=audio sections accepted on one recording session. A session offering more is rejected with 486, bounding the RTP ports and goroutines a single peer can claim. |
SIPREC_METADATA_MAX_BYTES | 65536 | Maximum size of the rs-metadata XML document in a SIPREC INVITE. A larger document is rejected with 413 rather than parsed. |
SIPREC_SRC_ENABLED | false | Allow POST /v1/rooms/{id}/siprec to originate outbound recording sessions, forking a room's participants to an external session recording server. Off by default: it lets an API caller stream a room's audio to an arbitrary SIP destination. |
SIPREC_AUTO_RECORD | false | Start multi-channel recording automatically when a SIPREC session is accepted, one channel per recorded participant. When false, recording is driven through the usual /v1/legs/{id}/record endpoint. |
SIPREC_ROOM_MODE | none | Where a recording session's audio streams are mixed. "none" attaches nothing and leaves placement to the stream API; "per_session" creates a room named siprec-<legID> and attaches every stream, which is what makes live STT and agents apply to a recorded call; "fixed" attaches every session's streams into SIPREC_ROOM_ID. |
SIPREC_ROOM_ID | — | Room every recording session's streams join when SIPREC_ROOM_MODE=fixed. Ignored in the other modes. |
MOQ_ENABLED | false | Enable the experimental MoQ (Media over QUIC) inbound leg endpoint at CONNECT /v1/legs/moq over WebTransport/HTTP/3. PoC quality, tracks IETF draft-11. When enabled, both MOQ_TLS_CERT_FILE and MOQ_TLS_KEY_FILE must be set. |
MOQ_LISTEN_ADDR | :8443 | UDP address for the HTTP/3 listener that backs the MoQ leg. Independent of HTTP_ADDR — TCP/:8080 and UDP/:8443 can run side-by-side. |
MOQ_TLS_CERT_FILE | — | Path to the TLS certificate used by the HTTP/3 listener. Required when MOQ_ENABLED=true. |
MOQ_TLS_KEY_FILE | — | Path to the TLS private key used by the HTTP/3 listener. Required when MOQ_ENABLED=true. |
MOQ_OPUS_BITRATE | 24000 | Target bitrate (bps) for the Opus encoder feeding the MoQ leg's mix track. Must be in 6000..510000. |
LIVEKIT_ENABLED | false | Enable the livekit_room leg type at POST /v1/legs (type=livekit_room). Lets VoiceBlender join a LiveKit room as a participant and bridge audio between SIP and LiveKit. Speaks the LiveKit signaling protocol directly via livekit/protocol protobufs over the existing pion stack — no LiveKit SDK is used. |
LIVEKIT_URL | — | Default LiveKit server endpoint (wss://...). Required when LIVEKIT_ENABLED=true unless every request supplies livekit.url. Overridable per-request. |
LIVEKIT_OPUS_BITRATE | 24000 | Target bitrate (bps) for the Opus encoder publishing audio into LiveKit. Must be in 6000..510000. Overridable per-request via livekit.opus_bitrate. |
LIVEKIT_TOKEN_SIGNING_ENABLED | false | Opt-in: when true, callers may omit livekit.token and instead pass {room,identity,permissions}; VoiceBlender mints the JWT itself. Security caveat: enabling this stores the LiveKit API secret (a high-privilege credential) in VoiceBlender. Keep off in multi-tenant deployments. |
LIVEKIT_API_KEY | — | LiveKit API key used to sign minted JWTs. Required only when LIVEKIT_TOKEN_SIGNING_ENABLED=true. |
LIVEKIT_API_SECRET | — | LiveKit API secret used to sign minted JWTs. Required only when LIVEKIT_TOKEN_SIGNING_ENABLED=true. Treat as a high-value secret; redact in logs. |
LIVEKIT_DEFAULT_TOKEN_TTL | 6h | Default TTL applied to minted JWTs when the request omits livekit.token_ttl. Go duration string. LiveKit recommends ≤ 6 hours. |