whip2wowza

February 8, 2026 ยท View on GitHub

WHIP-to-Wowza gateway. Enables standard WHIP clients to publish to Wowza Streaming Engine or Wowza Cloud.

What it does

Adds WHIP protocol support to Wowza using Pion WebRTC, a modern Go-based WebRTC stack. The gateway receives media from WHIP clients and relays it to Wowza.

Wowza uses proprietary WebSocket signaling instead of the WHIP standard, so WHIP clients (OBS, GStreamer, browser SDKs) can't connect directly. There are also connection reliability issues:

  • Wowza Cloud requires TCP on port 1935
  • Wowza Media Engine supports UDP, but some networks and ISPs experience connection failures that don't occur with other WebRTC implementations
  • Firefox isn't supported - Wowza's publisher page disables it, and the SDK is incompatible
  • No STUN/TURN for NAT traversal

This gateway bridges these gaps, enabling any WHIP client to publish to Wowza with better connection reliability across different networks and browsers.

Since the gateway is stateless and lightweight, it can be deployed at multiple edge locations with Anycast or GeoDNS to route publishers to the nearest ingest point. All gateways can feed into a single Wowza origin, improving connectivity for geographically distributed clients.

How it works

The gateway maintains two WebRTC peer connections - one facing the client (using Pion), one facing Wowza:

WHIP Client  <--WebRTC-->  whip2wowza  <--WebRTC-->  Wowza
                                |
                          Pion WebRTC
  1. Client sends WHIP POST with SDP offer
  2. Gateway negotiates WebRTC using Pion, returns SDP answer
  3. Client starts sending media; gateway waits for first video packet
  4. Gateway connects to Wowza's WebSocket endpoint and negotiates a second WebRTC session
  5. RTP/RTCP packets are relayed between both connections
  6. DELETE request tears down the session

The gateway handles SDP transformation (filtering private IPs, removing unsupported extensions), codec selection, and H.264 NAL repacketization automatically.

Playback in Wowza

Quick start

Requires Go 1.24+.

go build -o whip2wowza .
./whip2wowza

The gateway starts on :8080 by default.

Browser publishing

Open the built-in test page:

http://localhost:8080/static/wowzacloud.html

Enter your Wowza host, application name, and stream name. Click publish.

Built-in Web UI

WHIP clients (OBS, GStreamer, etc.)

Endpoint URL:

http://localhost:8080/api/cloud/{host}/{app}

{host} can be a Wowza Cloud ID (no dots) or a full hostname:

  • c334d88b12ab expands to c334d88b12ab.entrypoint.cloud.wowza.com
  • c334d88b12ab.entrypoint.cloud.wowza.com used as-is
  • wowza.example.com used as-is (on-prem)

Set the Bearer token to your stream name:

Authorization: Bearer myStream

Or with Wowza token authentication:

Authorization: Bearer myStream?token=secret123

The gateway passes the Bearer value to Wowza unchanged.

Example with curl:

curl -X POST \
  -H "Content-Type: application/sdp" \
  -H "Authorization: Bearer myStream" \
  --data-binary @offer.sdp \
  http://localhost:8080/api/cloud/c334d88b12ab/live

# Returns 201 Created
# Location: /api/cloud/c334d88b12ab/live/session-abc123

To stop the session:

curl -X DELETE http://localhost:8080/api/cloud/c334d88b12ab/live/session-abc123

Static mode

Lock the gateway to a single Wowza host at startup:

# Wowza Cloud
./whip2wowza --websocket "wss://c334d88b12ab.entrypoint.cloud.wowza.com/webrtc-session.json"

# Wowza Media Engine
./whip2wowza --websocket "wss://wowza.example.com/webrtc-session.json"

Endpoints

Dynamic mode (default)

MethodPathDescription
POST/api/cloud/{host}/{app}Publish, prefer H.264
POST/api/cloud-vp8/{host}/{app}Publish, prefer VP8
DELETE/api/cloud/{host}/{app}/session-{id}Stop session

Static mode

MethodPathDescription
POST/api/whip/{app}Publish, prefer H.264
POST/api/whip-vp8/{app}Publish, prefer VP8
DELETE/api/whip/{app}/session-{id}Stop session

Utility

MethodPathDescription
GET/healthHealth check with session count
GET/statsPer-session statistics (JSON)

Configuration

FlagEnvDefaultDescription
--whip-addressWHIP_ADDRESS:8080HTTP bind address
--websocketWOWZA_WEBSOCKET_URL-Fixed Wowza URL (enables static mode)
--allowed-hostsALLOWED_HOSTS*Host allowlist for dynamic mode
--ice-udp-mux-portICE_UDP_MUX_PORT0Single UDP port for ICE (0 = use range)
--ice-tcp-mux-portICE_TCP_MUX_PORT0Single TCP port for ICE (0 = disabled)
--enable-ipv6ENABLE_IPV6falseEnable IPv6 ICE candidates on WHIP ingest side
--nat-1to1-ipNAT_1TO1_IP-Comma-separated public IPs for ICE host candidates (must include IPv4 for Wowza relay)
--h264-repacketizeH264_REPACKETIZEautoNAL repacketization: off, on, auto
--max-sessionsMAX_SESSIONS0Max concurrent sessions (0 = unlimited)
--video-ready-timeoutVIDEO_READY_TIMEOUT15sTimeout for first video packet
--sr-intervalSR_INTERVAL5sRTCP Sender Report interval
--video-bitrate-kbpsVIDEO_BITRATE_KBPS800SDP bandwidth hint
--audio-bitrate-kbpsAUDIO_BITRATE_KBPS96SDP bandwidth hint
--video-fpsVIDEO_FPS30SDP framerate hint
--verboseVERBOSEfalseDebug logging
--insecure-tlsINSECURE_TLSfalseSkip TLS verification

Environment variables:

EnvDefaultDescription
UDP_PORT_MIN10000Start of UDP port range
UDP_PORT_MAX12000End of UDP port range
LOG_FORMATautotext, json, or auto (text for TTY)
ENABLE_IPV6falseEnable IPv6 ICE candidates on WHIP ingest side
NAT_1TO1_IP-Comma-separated public IPs for ICE host candidates (must include IPv4 for Wowza relay)

Networking

IPv6 affects WHIP ingest ICE only; Wowza relay candidates remain IPv4 after SDP filtering.

Port range mode (default):

  • Uses UDP ports 10000-12000
  • Each session gets its own port
  • Best for VMs, bare-metal, or Docker/Kubernetes with host networking
  • Firewall must allow the full port range

Single port mode:

  • All WebRTC traffic multiplexed on one UDP/TCP port
  • Better for containerized deployments without host networking
  • Minor CPU overhead for demultiplexing
./whip2wowza --ice-udp-mux-port 8443 --ice-tcp-mux-port 8443

Docker

Build:

docker build -t whip2wowza .

The included Dockerfile uses a two-stage build with scratch base for minimal image size (~15MB).

With host networking (uses port range mode):

docker run --network host whip2wowza

With port mapping (uses single port mode):

docker run -p 8080:8080 -p 8443:8443/udp -p 8443:8443/tcp \
  whip2wowza --ice-udp-mux-port 8443 --ice-tcp-mux-port 8443

Browser support

ClientH.264VP8Notes
ChromeYesYes
EdgeYesYes
FirefoxYesYesPacketization-mode 0 handled
SafariYesYesUse VP8 endpoint if H.264 fails
OBS WHIPYesYes

H.264 profile preference: Constrained Baseline > Baseline > Main > High

Security

Dynamic mode allows connections to any Wowza host by default. Restrict in production:

./whip2wowza --allowed-hosts "c334d88b12ab,*.example.com"

The gateway serves HTTP only. Use a reverse proxy (nginx, Caddy, Kubernetes Ingress, etc.) for TLS termination.

Troubleshooting

No video: Check logs for Wowza ICE state: connected. If ICE fails, verify firewall allows UDP 10000-12000 (or your configured mux port).

Connection refused: Verify hostname format and Authorization header.

Firefox issues: Logs should show PM=0 detection. Restart the publish to request a new keyframe.

Safari H.264 problems: Try the VP8 endpoints (/api/cloud-vp8/... or /api/whip-vp8/...).

License

MIT