Skip to content

nest: Google SDM GenerateWebRtcStream now returns 400 — offerSdp validation tightened (2026) #2311

Description

@dougdooley

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:

  1. offerSdp is required — calling without it returns:
    400 "offerSdp value must be set. This is a required field."

  2. 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."

  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions