Summary
Google's SDM API (GenerateWebRtcStream) has tightened offer validation (observed June 2026). Nest cameras that streamed fine via go2rtc 1.9.14 now get HTTP 400. The /api/nest diagnostic endpoint returns nest: wrong status: 400 Bad Request.
Environment
- go2rtc version: 1.9.14 (Jan 19 2026 build, the current release)
- Cameras: Google Nest Cam (indoor/outdoor), multiple units
- Config: standard
nest:?client_id=...&client_secret=...&refresh_token=...&project_id=...&device_id=... source
Symptom
WRN [nest] error="nest: wrong status: 400 Bad Request"
Cameras work fine in Google Home / the Google portal. OAuth token refresh succeeds. SDM device listing returns all cameras correctly.
Root Cause (confirmed via direct API testing)
Google's GenerateWebRtcStream endpoint now enforces strict SDP offer validation:
-
offerSdp is required — calling without it returns:
400 "offerSdp value must be set. This is a required field."
-
The SDP must contain exactly audio + video + application m-lines, in that order — anything else (e.g. video-only) returns:
400 "Invalid offer SDP: Offer must contain each of audio, video and application m lines in that order."
-
A real ICE host candidate must be present in the offer — without one, Google returns 500.
A hand-crafted offer with all three m-lines (audio recvonly, video recvonly, application/datachannel) and a real local UDP candidate gets a valid 200 SDP answer with ICE credentials and DTLS fingerprint.
What go2rtc 1.9.14 sends
Looking at pkg/nest/client.go, rtcConn() builds medias including {Kind: "app"} and calls conn.CreateCompleteOffer(medias). The issue appears to be in what CreateCompleteOffer actually generates — the resulting SDP either lacks a real ICE host candidate (substituting 0.0.0.0 or no candidate at all) or is otherwise malformed in a way that Google's stricter 2026 validation rejects.
The code correctly passes the offer via offerSdp in ExchangeSDP — so that part is fine. The problem is the offer content itself.
Suggested Investigation
Check whether CreateCompleteOffer (via pion/webrtc) waits for ICE gathering to complete and includes a=candidate lines with real host IPs in the resulting SDP. Google requires at least one a=candidate with a real UDP host address — 0.0.0.0 or a pure a=end-of-candidates with no candidates appears to cause the 500/400.
Workaround
None within go2rtc. As a workaround, using protocols=RTSP in the nest URL (if supported by the specific camera model) bypasses WebRTC entirely.
Summary
Google's SDM API (
GenerateWebRtcStream) has tightened offer validation (observed June 2026). Nest cameras that streamed fine via go2rtc 1.9.14 now get HTTP 400. The/api/nestdiagnostic endpoint returnsnest: wrong status: 400 Bad Request.Environment
nest:?client_id=...&client_secret=...&refresh_token=...&project_id=...&device_id=...sourceSymptom
Cameras work fine in Google Home / the Google portal. OAuth token refresh succeeds. SDM device listing returns all cameras correctly.
Root Cause (confirmed via direct API testing)
Google's
GenerateWebRtcStreamendpoint now enforces strict SDP offer validation:offerSdpis required — calling without it returns:400 "offerSdp value must be set. This is a required field."The SDP must contain exactly audio + video + application m-lines, in that order — anything else (e.g. video-only) returns:
400 "Invalid offer SDP: Offer must contain each of audio, video and application m lines in that order."A real ICE host candidate must be present in the offer — without one, Google returns 500.
A hand-crafted offer with all three m-lines (audio recvonly, video recvonly, application/datachannel) and a real local UDP candidate gets a valid
200SDP answer with ICE credentials and DTLS fingerprint.What go2rtc 1.9.14 sends
Looking at
pkg/nest/client.go,rtcConn()builds medias including{Kind: "app"}and callsconn.CreateCompleteOffer(medias). The issue appears to be in whatCreateCompleteOfferactually generates — the resulting SDP either lacks a real ICE host candidate (substituting0.0.0.0or no candidate at all) or is otherwise malformed in a way that Google's stricter 2026 validation rejects.The code correctly passes the offer via
offerSdpinExchangeSDP— so that part is fine. The problem is the offer content itself.Suggested Investigation
Check whether
CreateCompleteOffer(via pion/webrtc) waits for ICE gathering to complete and includesa=candidatelines with real host IPs in the resulting SDP. Google requires at least onea=candidatewith a real UDP host address —0.0.0.0or a purea=end-of-candidateswith no candidates appears to cause the 500/400.Workaround
None within go2rtc. As a workaround, using
protocols=RTSPin the nest URL (if supported by the specific camera model) bypasses WebRTC entirely.