Local WebRTC / RTSP / HLS bridge for Wyze cameras. No modifications, no special firmware, no cloud dependency for streaming (TUTK + OG cameras stay entirely on-LAN; doorbell-lineage cameras use Wyze's own WebRTC signaling but the media still flows directly to you).
Built in pure Go on top of go2rtc.
No Python, no binary SDKs, no C code beyond the ffmpeg used for
snapshot extraction and MP4 recording. Docker image ~65 MB.
Important
You need a Wyze Developer API ID and Key, not just your account password. Get them from the Wyze Developer Console.
Warning
Do not forward the bridge's ports on your public router/firewall or enable DMZ access. The WebUI and RTSP endpoints are designed for local use. For remote access, use a VPN (Tailscale, Wireguard, OpenVPN) or put a reverse proxy with authentication in front.
Upgrading from the Python bridge (mrlt8 v2.x / earlier 3.x)? See MIGRATION.md for the env-var rename table, breaking changes, and dropped features.
Click any thumbnail for the full-size image. Tall screenshots are
cropped to the camera-row height — click through to see the
complete page. Metrics are also exposed as JSON at /api/metrics.
All Cameras |
Single Camera |
Metrics — top |
Metrics — bottom |
Prometheus export at /metrics.prom
|
|
Three streaming paths, picked automatically per camera — you don't configure the path, the bridge routes based on the model.
- TUTK — go2rtc's built-in
wyze://source. Direct LAN P2P (or cloud-relayed P2P if the camera is remote) for most of the fleet. - Gwell P2P —
gwell-proxysidecar handles Wyze's newer Gwell/IoTVideo LAN-direct UDP protocol. Enabled by default (setGWELL_ENABLED=falseto opt out); the sidecar only actually spawns when an OG-family camera is discovered, so users without OG cameras pay zero cost. - WebRTC (KVS) — go2rtc's native
#format=wyzehandler dials Wyze'swyze-mars-webcsrv.wyzecam.comsignaling server itself and speaks AWS-KVS WebRTC. Used by doorbell-lineage hardware that skips both TUTK and Gwell P2P.
| Camera | Wyze ID | Path | Status |
|---|---|---|---|
| Wyze Cam V1 (HD only) | WYZEC1 |
TUTK | Should work |
| Wyze Cam V2 | WYZEC1-JZ |
TUTK | Should work |
| Wyze Cam V3 | WYZE_CAKP2JFUS |
TUTK | Confirmed |
| Wyze Cam V3 Pro (2K) | HL_CAM3P |
TUTK | Should work |
| Wyze Cam V4 (2K) | HL_CAM4 |
TUTK | Confirmed |
| Wyze Cam Floodlight | WYZE_CAKP2JFUS |
TUTK | Should work |
| Wyze Cam Floodlight V2 (2K) | HL_CFL2 |
TUTK | Should work |
| Wyze Cam Pan | WYZECP1_JEF |
TUTK | Should work |
| Wyze Cam Pan V2 | HL_PAN2 |
TUTK | Should work |
| Wyze Cam Pan V3 | HL_PAN3 |
TUTK | Mostly working |
| Wyze Cam Pan Pro (2K) | HL_PANP |
TUTK | Should work |
| Wyze Cam Outdoor | WVOD1 |
TUTK | Should work |
| Wyze Cam Outdoor V2 | HL_WCO2 |
TUTK | Should work |
| Wyze Cam Doorbell V1 | WYZEDB3 |
TUTK | Needs support in go2rtc |
| Wyze Cam Doorbell V2 (2K) | HL_DB2 |
TUTK | Confirmed |
| Wyze Cam Doorbell Pro | GW_BE1 |
WebRTC | Confirmed 2026-04-20 |
| Wyze Cam Doorbell Duo | GW_DBD |
WebRTC | Expected (same lineage) |
| Wyze Cam OG | GW_GC1 |
Gwell P2P | Expected (LAN-direct UDP) |
| Wyze Cam OG Telephoto 3X | GW_GC2 |
Gwell P2P | Expected (LAN-direct UDP) |
| Wyze Battery Cam Pro | AN_RSCW |
— | Not supported |
| Wyze Cam Floodlight Pro (2K) | LD_CFP |
— | Not supported |
"Expected" means the code path is plumbed and equivalent hardware is known to work, but we don't own a unit to confirm end-to-end. Bug reports welcome.
docker run -p 5080:5080 -p 8554:8554 -p 8888:8888 -p 8889:8889 -p 8189:8189/udp \
-e WYZE_EMAIL=you@example.com \
-e WYZE_PASSWORD=yourpass \
-e WYZE_API_ID=your-api-id \
-e WYZE_API_KEY=your-api-key \
idisposablegithub365/wyze-bridge:goThen open http://localhost:5080 for the WebUI.
services:
wyze-bridge:
image: idisposablegithub365/wyze-bridge:go
restart: unless-stopped
ports:
- 5080:5080 # WebUI + REST API
- 8554:8554 # RTSP
- 8888:8888 # HLS
- 8889:8889 # WebRTC HTTP
- 8189:8189/udp # WebRTC ICE
volumes:
- ./config:/config # state, go2rtc.yaml
- ./media:/media # snapshots + recordings land here
environment:
- WYZE_EMAIL=you@example.com
- WYZE_PASSWORD=yourpass
- WYZE_API_ID=your-api-id
- WYZE_API_KEY=your-api-key
# - BRIDGE_IP=192.168.1.50 # Required for WebRTC in-browser playback
# - STREAM_AUTH=viewer:secret # See Authentication section
# - RECORD_ALL=true
# - MQTT_HOST=homeassistant.local # Enables MQTT auto-discoveryEach camera is available at:
| Protocol | URL |
|---|---|
| RTSP | rtsp://HOST:8554/<camera_name> |
| HLS | http://HOST:8888/<camera_name> |
| WebRTC (browser) | http://HOST:5080/camera/<camera_name> |
| Snapshot (JPEG) | http://HOST:5080/api/snapshot/<camera_name> |
Camera names are normalized: lowercase, spaces replaced with underscores
(Front Door → front_door).
Click the badge (on a machine that can reach your HA instance) to
open the add-on store with this repo pre-filled. Then pick a channel:
Wyze Bridge (stable, tracks main) or Wyze Bridge (Edge)
(experimental, tracks dev). Setup docs live next to each add-on:
stable /
edge. If you also have
the Mosquitto broker add-on, cameras appear automatically via MQTT
discovery.
Auto-created entities per camera:
camera.<mac>— live stream + snapshotswitch.<mac>_audio,switch.<mac>_quality— runtime controlsbinary_sensor.<mac>_recording— flips with the record button
Bridge-wide entities:
sensor.bridge_cameras/sensor.bridge_streaming/sensor.bridge_errored— countssensor.bridge_uptime— seconds since startsensor.bridge_recordings_size— total MP4 bytessensor.bridge_config_errors— non-zero = check the/metricspage
The add-on's run.sh also drops an auto-generated Lovelace dashboard
at /config/wyze_bridge_dashboard.yaml at startup — add it as a
dashboard resource in HA and you get a ready-made view with glance
cards for every camera, no manual config.
Everything is controlled by environment variables. See MIGRATION.md for the full reference; the common ones:
| Variable | Required? | Description |
|---|---|---|
WYZE_EMAIL |
yes | Wyze account email |
WYZE_PASSWORD |
yes | Wyze account password |
WYZE_API_ID |
yes | API ID from Wyze Developer Console |
WYZE_API_KEY |
yes | API Key from Wyze Developer Console |
WYZE_TOTP_KEY |
optional | TOTP secret for accounts with 2FA enabled |
BRIDGE_IP |
for WebRTC | Host IP that browsers reach the bridge at; injected as a WebRTC ICE candidate |
MQTT_HOST |
for MQTT | MQTT broker hostname (enables MQTT) |
LOG_LEVEL |
no | trace / debug / info / warn / error (default info) |
Uppercase the normalized camera name, then prefix:
QUALITY_FRONT_DOOR=sd # hd (default) or sd
AUDIO_BACKYARD=false # audio off for this camera
RECORD_GARAGE=true # recording on for just this cameraFILTER_NAMES=Front Door,Backyard # Include these cameras only
FILTER_BLOCKS=true # Or exclude the listed ones
FILTER_MODELS=WYZECP1_JEF # By model code (comma-separated)
FILTER_MACS=AA:BB:CC:DD:EE:FF # By MACJPEG frames grabbed periodically or on sunrise/sunset events via go2rtc's frame API.
SNAPSHOT_INTERVAL=60s # periodic capture interval; 0 disables
SNAPSHOT_PATH=/media/snapshots/{cam_name}/%Y-%m-%d
SNAPSHOT_FILE_NAME=%H-%M-%S # no extension; .jpg is appended
SNAPSHOT_KEEP=14d # retention; 0 = keep forever
SNAPSHOT_CAMERAS=front_door,backyard # optional allowlist
LATITUDE=37.7749 # enables sunrise/sunset capture
LONGITUDE=-122.4194ffmpeg -c copy -f segment per recording-enabled camera, pulling from
our own RTSP endpoint on loopback. Runs only while the camera is
streaming; stops automatically when the camera goes offline.
RECORD_ALL=true # or RECORD_<CAM>=true
RECORD_PATH=/media/recordings/{cam_name}/%Y/%m/%d
RECORD_FILE_NAME=%H-%M-%S # no extension; .mp4 is appended
RECORD_LENGTH=60s # segment duration
RECORD_KEEP=7d # retention; 0 = keep foreverReplace the built-in ffmpeg argv entirely when you need a different container, hardware encoder, metadata overlay, or anything else:
# Global — all recording-enabled cameras use this
RECORD_CMD=ffmpeg -hide_banner -rtsp_transport tcp -i {rtsp_url} -c:v h264_nvenc -preset p6 -b:v 4M -f segment -segment_time {segment_sec} -strftime 1 {output_stem}.webm
# Or per-camera — this wins over the global for one camera
RECORD_CMD_FRONT_DOOR=sh -c 'ffmpeg -i {rtsp_url} -c copy {output} | tee /mnt/archive/front_door.mp4'Supported tokens:
| Token | Value |
|---|---|
{cam_name} / {CAM_NAME} |
Normalized camera name (lower/upper) |
{rtsp_url} |
rtsp://127.0.0.1:8554/<cam_name> — our loopback RTSP |
{rtsp_host} / {rtsp_port} |
Host + port separately, for custom URL schemes |
{output} |
Expanded RECORD_PATH/RECORD_FILE_NAME.mp4 |
{output_dir} / {output_stem} |
Split of the above — stem is everything before .mp4 so you can write a different extension |
{segment_sec} |
RECORD_LENGTH in integer seconds |
{quality} |
hd or sd per camera |
Invocation semantics:
- The template is tokenized respecting double/single quotes (no shell
by default, no variable expansion, no pipes). The program in the
first position is
exec'd directly. - For pipes / shell features, prefix with
sh -c "..."— you're opting in explicitly. - Unknown tokens (like
{typo_here}) fail parsing at the first spawn attempt; the bridge falls back to the built-in default argv for that camera and raises a config error visible on/metricsand/api/health. Recording for other cameras is unaffected.
BRIDGE_AUTH=true+BRIDGE_USERNAME+BRIDGE_PASSWORD— gates the WebUI + REST API.STREAM_AUTH=viewer:secret— RTSP / HLS / WebRTC streams require these credentials. Per-camera auth is not supported in this rewrite.
| Port | Purpose |
|---|---|
| 5080 | Bridge WebUI + REST API |
| 1984 | go2rtc native UI (optional; useful for probing/debugging) |
| 8554 | RTSP |
| 8888 | HLS |
| 8889 | WebRTC HTTP |
| 8189/udp | WebRTC ICE |
The bridge exposes several operational endpoints at http://HOST:5080:
| Path | Purpose |
|---|---|
/metrics |
Human-readable HTML dashboard: issues panel, bridge summary, per-camera table, Wyze cloud API call stats, storage footprint, recent events log. Auto-refreshes every 10 seconds. |
/metrics.prom |
Prometheus-format exposition. Gauges + counters under the wyze_bridge_ prefix with labels for camera, protocol, endpoint. Unauthenticated by default so Grafana / VictoriaMetrics scrapers work without any extra config. |
/api/metrics |
Same snapshot as /metrics but as JSON — convenient for scripting or piping into other tooling. |
/api/health |
Compact health check: {status, version, uptime, config_errors, issues}. status flips to "degraded" whenever the issues list is non-empty, which maps cleanly to a HA binary_sensor. |
/dashboard.yaml |
Auto-generated Home Assistant Lovelace dashboard referencing the MQTT discovery entities the bridge publishes — one glance card per camera with snapshot, recording indicator, and audio/quality controls. See Home Assistant below. |
Every camera card in the WebUI (and the single-camera page) has a record pulse button. Clicking starts a per-camera ffmpeg segment writer pulling from loopback RTSP; clicking again stops it. The same toggle is available over:
- MQTT:
<topic>/<cam>/recording(ON/OFF, retained). HA discovery publishes abinary_sensorby default. - REST:
POST /api/cameras/<name>/recordwith body{"action":"start"|"stop"}(empty body = start). Returns the resulting state, so a client can render from the authoritative answer rather than optimistically.
Whatever toggles recording fires an SSE recording_state event, so
all open browser tabs + the metrics page update live.
When a subsystem hits a soft failure — a bad RECORD_PATH template,
an unreachable MQTT broker, a future RECORD_CMD with an unknown
token — it reports into the process-wide issues registry instead of
silently logging a warning. The metrics page renders open issues in a
red panel at the top; /api/health includes a config_errors count;
the MQTT wyze_bridge/bridge/config_errors topic lets HA wire an
alert on it.
How does this work?
For TUTK cameras, go2rtc speaks Wyze's P2P protocol directly (no SDK).
For OG cameras, a small gwell-proxy sidecar handles the Gwell/IoTVideo
handshake. For doorbell-lineage cameras (Doorbell Pro, Doorbell Duo),
go2rtc's built-in Wyze WebRTC handler calls our internal shim for the
KVS signaling URL and dials Wyze's server itself. The bridge
orchestrates auth, discovery, MQTT, and the WebUI.
Does it use internet bandwidth when streaming? For TUTK and Gwell cameras on the same LAN: effectively zero — media flows directly between the camera and the bridge over UDP. For WebRTC cameras (Doorbell Pro / Duo): the signaling goes through Wyze's cloud, and media may route through Wyze's AWS TURN servers if the direct path can't be negotiated.
Can this work offline? Streaming alone can survive without internet for TUTK/Gwell cameras already paired, but tokens and some commands eventually need the cloud. WebRTC cameras need the internet every session to fetch the signaling URL from Wyze.
Why isn't GW_BE1 / GW_DBD "supported" on upstream mrlt8?
Upstream is TUTK-only; doorbell-lineage cameras don't expose TUTK at
all. We handle them via Wyze's cloud WebRTC path, which upstream
doesn't implement.
What about Battery Cam Pro? Not yet. Uses a different protocol than any of the three we handle.
Docker Container
├── wyze-bridge (Go binary, port 5080)
│ ├── Wyze API Client — auth, camera discovery, get_streams
│ ├── go2rtc Manager — subprocess, skeletal YAML, AddStream API
│ ├── Camera Manager — state machines, reconnection, routing
│ ├── MQTT Client — HA discovery, commands, state
│ ├── WebUI Server — REST API, SSE, embedded UI, WebRTC shim
│ ├── Snapshot Manager — interval, sunrise/sunset, pruning
│ └── Recording Manager — ffmpeg per camera, segment pruning
├── go2rtc (managed sidecar, ports 1984 / 8554 / 8888 / 8889 / 8189)
│ ├── wyze:// source — TUTK P2P (built in)
│ ├── rtsp: source — receives publish from gwell-proxy
│ └── webrtc: source — native Wyze KVS (via our shim)
└── gwell-proxy (only spawned for OG cameras)
└── Gwell P2P → ffmpeg → RTSP PUBLISH to loopback go2rtc
go test ./... # run tests
go build -o wyze-bridge ./cmd/wyze-bridge
docker compose up --build # Docker build + run
./cycle.sh # local dev cycle with .env.devSee DEVELOPER.md for local dev / devcontainer setup.
This fork builds on work from several upstream projects:
idisposable/docker-wyze-bridgeakeslo/docker-wyze-bridgekroo/wyzecamAlexxIT/go2rtcwlatic/hacky-wyze-gwellidisposable/docker-wyze-bridge
The go2rtc sidecar uses github.com/AlexxIT/go2rtc, which is licensed under MIT.
The gwell sidecar uses a vendored copy of the Go protocol packages from github.com/wlatic/hacky-wyze-gwell, which is licensed under MIT.




