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
- Inspect the token for '%' sequences and ensure each is followed by two valid hex digits.
- If the token is already plain text (not URL-encoded), pass it without additional encoding rather than escaping again.
- Re-obtain a fresh refresh token from the Ring auth flow and store it uncorrupted.
- 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
- Store refresh tokens exactly as issued; do not double-encode.
- Escape '%' characters if your config/templating layer interprets them.
- Regenerate the token if it appears truncated or corrupted in storage.
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
- hass: no token
- failed to parse refresh token
- authentication failed after
- failed to refresh session
- authentication failed while creating session
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)