{"record":{"id":"8dd59b50c55b9dbe","repo":"wtfutil/wtf","slug":"errors-new-hibperr-message","errorCode":null,"errorMessage":"errors.New(hibpErr.Message)","messagePattern":"errors\\.New\\(hibpErr\\.Message\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"modules/hibp/client.go","lineNumber":63,"sourceCode":"\t\treturn nil, err\n\t}\n\n\trequest.Header.Set(\"User-Agent\", userAgent)\n\trequest.Header.Set(\"hibp-api-key\", widget.settings.apiKey)\n\n\tresponse, getErr := hibpClient.Do(request)\n\tif getErr != nil {\n\t\treturn nil, err\n\t}\n\n\tbody, readErr := io.ReadAll(response.Body)\n\tif readErr != nil {\n\t\treturn nil, err\n\t}\n\n\thibpErr := widget.validateHTTPResponse(response.StatusCode, body)\n\tif hibpErr != nil {\n\t\treturn nil, errors.New(hibpErr.Message)\n\t}\n\n\tstat, err := widget.parseResponseBody(account, body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn stat, nil\n}\n\nfunc (widget *Widget) parseResponseBody(account string, body []byte) (*Status, error) {\n\tbreaches := []Breach{}\n\tstat := NewStatus(account, breaches)\n\n\tif len(body) == 0 {\n\t\t// If the body is empty then there's no breaches\n\t\treturn stat, nil\n\t}","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/wtfutil/wtf/blob/bb838c1ccb0f0f3223690df44afdec663d622881/modules/hibp/client.go#L45-L81","documentation":"hibp fetchForAccount validates the HTTP response with validateHTTPResponse, which returns a structured error (status code + message). When validation fails, the returned hibpErr.Message is converted to a Go error and propagated to Fetch. Typical cases are 401 (invalid API key) and 429 (rate limit) from the Have I Been Pwned API.","triggerScenarios":"Missing or invalid HIBP API key ($hibpKey) causing 401; exceeding HIBP's request rate limits (429); malformed account queries; any non-success status detected by validateHTTPResponse.","commonSituations":"Free tier users without the required paid API key for breach searches; running many account checks rapidly and tripping rate limiting; token rotated/expired in the config; corporate proxy intercepting and returning error statuses.","solutions":["Set a valid HIBP API key in the widget config (a paid key is required for the breach API)","Reduce polling frequency to stay under HIBP rate limits","Check the propagated hibpErr.Message/status — 401 means key issue, 429 means slow down","Test manually: curl -H 'hibp-api-key: <key>' https://haveibeenpwned.com/api/v3/breachedaccount/<account>"],"exampleFix":"// before\nhibp:\n  apiKey: \"\"            // empty -> 401\n  refreshInterval: 30   // seconds -> 429\n// after\nhibp:\n  apiKey: \"<paid-api-key>\"\n  refreshInterval: 3600","handlingStrategy":"try-catch","validationCode":"if hibpKey == \"\" {\n    return errors.New(\"HIBP API key required (paid tier for breach lookup)\")\n}\nif time.Since(lastFetch) < time.Hour {\n    return errors.New(\"HIBP rate limit: throttle requests\")\n}","typeGuard":null,"tryCatchPattern":"breaches, err := fetchForAccount(account)\nif err != nil {\n    var httpErr *hibpHTTPError\n    if errors.As(err, &httpErr) && httpErr.StatusCode == 429 {\n        time.Sleep(backoff) // rate-limited: retry later\n        return fetchForAccount(account)\n    }\n    return err\n}","preventionTips":["Use a paid HIBP API key — the v3 breach API rejects free/absent keys","Honor Retry-After on 429 and keep refresh intervals ≥ hourly","Rotate and validate keys on schedule","Test with curl using the hibp-api-key header before wiring configs"],"tags":["go","api","http","hibp","rate-limit"],"backgroundTag":"upstream-api-error-response","analyzedSha":"bb838c1ccb0f0f3223690df44afdec663d622881","analyzedAt":"2026-09-03T17:02:45.030Z","contentChangedAt":"2026-09-03T17:02:45.030Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}