AlexxIT/go2rtc · error

ring: close

Error message

ring: close

What it means

When the ring WebSocket receives a "close" message from the server, the client stops itself and completes the pending connection with this "ring: close" error, indicating the remote side intentionally closed the session.

Solutions

  1. Refresh the Ring refresh_token and reconnect
  2. Check for other active sessions using the same camera
  3. Add automatic reconnect logic with fresh credentials
  4. Retry after a delay in case of server-side throttling

Example fix

// before
client, err := Dial(ctx, query) // fails with "ring: close"
// after
if err != nil && errors.Is(err, errRingClose) {
    query.Set("refresh_token", getFreshToken())
    client, err = Dial(ctx, query)
}
Defensive patterns

Strategy: retry

Validate before calling

if time.Since(tokenRefreshedAt) > maxTokenAge {
    query.Set("refresh_token", refreshRingToken()) // stale tokens cause server-initiated close
}

Try / catch

client, err := Dial(ctx, query)
if err != nil && err.Error() == "ring: close" {
    query.Set("refresh_token", getFreshToken())
    client, err = Dial(ctx, query)
}

Prevention

When it happens

Trigger: Ring's server sends a WebSocket message of type "close" — e.g. session rejected, token expired, concurrent session limit, or server-side teardown of the stream.

Common situations: Expired refresh token invalidating the live session; opening a second stream for the same camera; Ring server maintenance or rate limiting.

Related errors


AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07). Data as JSON: /api/errors/0c10ec1f7f5c4fb7. Report an issue: GitHub.

Appendix: source

Thrown at pkg/ring/client.go:301

			if err := json.Unmarshal(rawMsg, &msg); err != nil {
				break
			}

			// Skip empty candidates
			if msg.Body.Ice == "" {
				break
			}

			if err := prod.AddCandidate(msg.Body.Ice); err != nil {
				c.Stop()
				c.connected.Done(err)
				return
			}
		}

	case "close":
		c.Stop()
		c.connected.Done(errors.New("ring: close"))

	case "pong":
		// Ignore
	}
}

func (c *Client) GetMedias() []*core.Media {
	return c.prod.GetMedias()
}

func (c *Client) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, error) {
	return c.prod.GetTrack(media, codec)
}

func (c *Client) AddTrack(media *core.Media, codec *core.Codec, track *core.Receiver) error {
	if webrtcProd, ok := c.prod.(*webrtc.Conn); ok {
		if media.Kind == core.KindAudio {
			// Enable speaker

View on GitHub (pinned to c245815e75)