amir20/dozzle · error

empty key received

Error message

empty key received

What it means

After successfully decoding the token response, cloudCallback requires a non-empty `key` field; this key is what dozzle stores to authenticate with the cloud service. An empty key means the exchange technically succeeded but produced no credential, so dozzle refuses it with a 500.

Solutions

  1. Verify your Dozzle Cloud account/subscription is active and an API key is provisioned.
  2. Re-run the cloud connect flow to request a fresh exchange.
  3. Check dozzle server logs for the `Empty key received` entry and contact support if the account is valid.

Example fix

// before
{"key":""}
// after
{"key":"abc123...","expiresAt":"2026-01-01T00:00:00Z"}
Defensive patterns

Strategy: validation

Validate before calling

const data = await res.json();
if (!data.key) {
  // stop: exchange returned no credential; verify account/subscription before retry
}

Type guard

function hasKey(v: { key?: string }): v is { key: string } {
  return typeof v.key === 'string' && v.key.length > 0;
}

Prevention

When it happens

Trigger: The cloud exchange endpoint returns 200 with valid JSON but `key` is "" or the field is absent, e.g. account not provisioned, subscription inactive, or a cloud-side bug returning a success envelope without a key.

Common situations: Dozzle Cloud subscription lapsed or not active for this account; API key revoked on the cloud side while old tokens still validate; mismatched cloud service version returning a new response shape.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/ca3045e9105f68d9. Report an issue: GitHub.

Appendix: source

Thrown at internal/web/cloud.go:95

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
		log.Error().Int("status", resp.StatusCode).Str("body", string(body)).Msg("Token exchange failed")
		http.Error(w, "token exchange failed", http.StatusInternalServerError)
		return
	}

	var tokenResp exchangeTokenResponse
	if err := json.NewDecoder(resp.Body).Decode(&tokenResp); err != nil {
		log.Error().Err(err).Msg("Failed to decode token response")
		http.Error(w, "failed to decode token response", http.StatusInternalServerError)
		return
	}

	if tokenResp.Key == "" {
		log.Error().Msg("Empty key received")
		http.Error(w, "empty key received", http.StatusInternalServerError)
		return
	}

	var expiresAt *time.Time
	if tokenResp.ExpiresAt != nil {
		parsed, err := time.Parse(time.RFC3339, *tokenResp.ExpiresAt)
		if err != nil {
			log.Warn().Err(err).Str("expiresAt", *tokenResp.ExpiresAt).Msg("Failed to parse expiresAt, ignoring")
		} else {
			expiresAt = &parsed
		}
	}

	// Save cloud config (also creates the cloud dispatcher and broadcasts to agents)
	cc := &notification.CloudConfig{
		APIKey:    tokenResp.Key,
		Prefix:    tokenResp.Prefix,
		ExpiresAt: expiresAt,

View on GitHub (pinned to d9463cbe21)