{"record":{"id":"20a2def465805ff2","repo":"AlexxIT/go2rtc","slug":"multitrans-talkback-failed-res-status","errorCode":null,"errorMessage":"multitrans: talkback failed: ${res.Status}","messagePattern":"multitrans: talkback failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/multitrans/client.go","lineNumber":180,"sourceCode":"}\n\nfunc (c *Client) openTalkChannel(uri, session string) error {\n\tpayload := `{\"type\":\"request\",\"seq\":0,\"params\":{\"method\":\"get\",\"talk\":{\"mode\":\"full_duplex\"}}}`\n\n\tdata := fmt.Sprintf(\"MULTITRANS %s RTSP/1.0\\r\\nCSeq: 2\\r\\nSession: %s\\r\\nContent-Type: application/json\\r\\nContent-Length: %d\\r\\n\\r\\n%s\",\n\t\turi, session, len(payload), payload)\n\n\tif _, err := c.conn.Write([]byte(data)); err != nil {\n\t\treturn err\n\t}\n\n\tres, err := tcp.ReadResponse(c.rd)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif res.StatusCode != http.StatusOK {\n\t\treturn errors.New(\"multitrans: talkback failed: \" + res.Status)\n\t}\n\n\t// Python checks for \"error_code\":0 in body.\n\tif !bytes.Contains(res.Body, []byte(`\"error_code\":0`)) {\n\t\treturn fmt.Errorf(\"multitrans: talkback error: %s\", string(res.Body))\n\t}\n\n\treturn nil\n}\n\nfunc (c *Client) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, error) {\n\treturn nil, core.ErrCantGetTrack\n}\n\nfunc (c *Client) Start() error {\n\t_ = c.closed.Wait()\n\treturn nil\n}","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/AlexxIT/go2rtc/blob/c245815e75e2a5fd60b4290f12bfc04e55a984d3/pkg/multitrans/client.go#L162-L198","documentation":"The final leg of the multitrans handshake, openTalkChannel, sends the talkback request and requires a 200 OK response. Any other status means the talk channel could not be opened; the server's status line is embedded in the error. Note this is distinct from a 200 body containing a non-zero error_code, which raises 'talkback error' instead.","triggerScenarios":"Dial -> handshake -> openTalkChannel; the talkback HTTP request returns a status other than 200 (e.g. 401, 403, 500).","commonSituations":"Session expired between auth and talkback (another client took the session); device limits concurrent talk sessions; insufficient user permissions for two-way audio; device rebooted mid-handshake.","solutions":["Re-run Dial from scratch to obtain a fresh session, then retry.","Ensure no other client/process is holding the device's talk session concurrently.","Verify the device account has permission for audio talkback (not just viewing).","Check device logs and firmware version; upgrade if talkback endpoint behavior changed."],"exampleFix":"// before\nerr := client.Dial(ctx) // one-shot; session may expire on retry loops\n\n// after\nfor i := 0; i < 3; i++ {\n    if err := client.Dial(ctx); err == nil { break }\n    time.Sleep(time.Second) // re-handshake to get a fresh session\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"var err error\nfor attempt := 0; attempt < 3; attempt++ {\n    if err = client.Dial(ctx); err == nil { break }\n    if strings.Contains(err.Error(), \"talkback failed\") {\n        time.Sleep(time.Duration(1<<attempt) * time.Second) // fresh session each try\n        continue\n    }\n    return err\n}","preventionTips":["Ensure only one talk client per device at a time.","Grant the device account talk/audio permissions, not just view.","Re-handshake instead of reusing sessions across long idle periods.","Monitor device reboots and back off during them."],"tags":["http","talkback","session","device"],"backgroundTag":"http-error-response","analyzedSha":"c245815e75e2a5fd60b4290f12bfc04e55a984d3","analyzedAt":"2026-09-07T11:47:02.965Z","contentChangedAt":"2026-09-07T11:47:02.965Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}