{"record":{"id":"18584e46f7d16f89","repo":"chenhg5/cc-connect","slug":"unauthorized-18584e","errorCode":null,"errorMessage":"unauthorized","messagePattern":"unauthorized","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"core/webhook.go","lineNumber":93,"sourceCode":"\t}()\n}\n\nfunc (ws *WebhookServer) Stop() {\n\tif ws.server != nil {\n\t\tctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n\t\tdefer cancel()\n\t\t_ = ws.server.Shutdown(ctx)\n\t}\n}\n\nfunc (ws *WebhookServer) handleHook(w http.ResponseWriter, r *http.Request) {\n\tif r.Method != http.MethodPost {\n\t\thttp.Error(w, \"POST only\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\n\tif !ws.authenticate(r) {\n\t\thttp.Error(w, \"unauthorized\", http.StatusUnauthorized)\n\t\treturn\n\t}\n\n\tvar req WebhookRequest\n\tif err := json.NewDecoder(r.Body).Decode(&req); err != nil {\n\t\thttp.Error(w, \"invalid JSON: \"+err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif req.SessionKey == \"\" {\n\t\thttp.Error(w, \"session_key is required\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif req.Prompt == \"\" && req.Exec == \"\" {\n\t\thttp.Error(w, \"either prompt or exec is required\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tif req.Prompt != \"\" && req.Exec != \"\" {","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/chenhg5/cc-connect/blob/4000b2338aa6e850c99df54f8b0ed6ed7460b401/core/webhook.go#L75-L111","documentation":"HTTP 401 response from the webhook handler: the request failed the server's authenticate check (missing, malformed, or wrong shared secret/token), so the inbound prompt/exec request is rejected before decoding.","triggerScenarios":"POSTing to the hook endpoint without an Authorization header, with a wrong Bearer token, or with a token that does not match the WebhookServer's configured secret.","commonSituations":"Secret rotated in config but the calling service still uses the old one; curl test forgot the auth header; webhook sender configured without credentials; token copy-pasted with whitespace or quotes.","solutions":["Add the correct Authorization header (Bearer <webhook-token>) matching the server's configured secret.","Re-copy the token from config.toml after any change and restart/refresh the caller.","Check for stray whitespace/quotes in the token in both config and client.","Confirm the token actually reached the server (log headers server-side at debug level)."],"exampleFix":"// before\ncurl -X POST http://127.0.0.1:8849/hook -d '{\"prompt\":\"hi\"}'\n// after\ncurl -X POST http://127.0.0.1:8849/hook -H \"Authorization: Bearer $WEBHOOK_TOKEN\" -d '{\"session_key\":\"s1\",\"prompt\":\"hi\"}'","handlingStrategy":"validation","validationCode":"func webhookPayload(url, token string) error {\n    if token == \"\" { return errors.New(\"missing webhook token\") }\n    req, _ := http.NewRequest(http.MethodPost, url, nil)\n    req.Header.Set(\"Authorization\", \"Bearer \"+token)\n    return nil // inspect req before send\n}","typeGuard":"func isUnauthorized(resp *http.Response) bool { return resp != nil && resp.StatusCode == http.StatusUnauthorized }","tryCatchPattern":"resp, err := client.Do(req)\nif err == nil && resp.StatusCode == 401 {\n    return fmt.Errorf(\"webhook auth failed: token mismatch with WebhookServer secret\")\n}","preventionTips":["Keep the webhook token in a single config source read by all senders","Trim whitespace/quotes when copying tokens","Rotate the token and the sender together in one deploy","Log (redacted) presence of the Authorization header server-side at debug level"],"tags":["webhook","authentication","http-401"],"backgroundTag":"authentication-required","analyzedSha":"4000b2338aa6e850c99df54f8b0ed6ed7460b401","analyzedAt":"2026-09-06T11:45:09.575Z","contentChangedAt":"2026-09-06T11:45:09.575Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}