AlexxIT/go2rtc · error

ring: invalid refresh token encoding

Error message

ring: invalid refresh token encoding: %w

What it means

Dial() decodes the refresh token with url.QueryUnescape before authenticating with the Ring REST API. If encodedToken contains a malformed percent-escape (e.g. a lone '%' or invalid hex digits), QueryUnescape fails and Dial returns this wrapped error. The token must be URL-encoded when stored and is decoded once here.

Solutions

  1. Inspect the token for '%' sequences and ensure each is followed by two valid hex digits.
  2. If the token is already plain text (not URL-encoded), pass it without additional encoding rather than escaping again.
  3. Re-obtain a fresh refresh token from the Ring auth flow and store it uncorrupted.
  4. If your config layer expands '%', escape or disable interpolation for that value.

Example fix

// before
c, err := ring.Dial(ctx, camID, "abc%ZZdef")
// after
token := url.QueryEscape(refreshToken) // or use the raw, correctly-encoded token
c, err := ring.Dial(ctx, camID, token)
Defensive patterns

Strategy: validation

Validate before calling

if encodedToken == "" {
    return errors.New("refresh token must not be empty")
}
if _, err := url.QueryUnescape(encodedToken); err != nil {
    return fmt.Errorf("refresh token has invalid percent-encoding: %v", err)
}

Try / catch

client, err := ring.Dial(ctx, cameraID, encodedToken)
if err != nil {
    if strings.Contains(err.Error(), "invalid refresh token encoding") {
        // re-fetch or re-store the token before retrying
    }
    return err
}

Prevention

When it happens

Trigger: Calling ring.Dial with an encodedToken string containing invalid percent-encoding such as "%ZZ", a trailing "%", or a token that was double-encoded/corrupted in storage.

Common situations: Manually copying a refresh token and truncating it mid-escape; double-encoding a token that is already plain; storing the token in a config system that mangles '%' characters (e.g. templating engines that treat % specially).

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at pkg/ring/client.go:53

	deviceID := query.Get("device_id")
	_, isSnapshot := query["snapshot"]

	if encodedToken == "" || deviceID == "" || cameraID == "" {
		return nil, errors.New("ring: wrong query")
	}

	client := &Client{
		dialogID: uuid.NewString(),
	}

	client.cameraID, err = strconv.Atoi(cameraID)
	if err != nil {
		return nil, fmt.Errorf("ring: invalid camera_id: %w", err)
	}

	refreshToken, err := url.QueryUnescape(encodedToken)
	if err != nil {
		return nil, fmt.Errorf("ring: invalid refresh token encoding: %w", err)
	}

	client.api, err = NewRestClient(RefreshTokenAuth{RefreshToken: refreshToken}, nil)
	if err != nil {
		return nil, err
	}

	// Snapshot Flow
	if isSnapshot {
		client.prod = NewSnapshotProducer(client.api, client.cameraID)
		return client, nil
	}

	client.wsClient, err = StartWebsocket(client.cameraID, client.api)
	if err != nil {
		client.Stop()
		return nil, err
	}

View on GitHub (pinned to c245815e75)