{"record":{"id":"72dac730e340362f","repo":"bytebase/bytebase","slug":"failed-to-post-google-chat-webhook-status-code","errorCode":null,"errorMessage":"failed to POST Google Chat webhook, status code: %d, response body: %s","messagePattern":"failed to POST Google Chat webhook, status code: (.+?), response body: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/plugin/webhook/googlechat/googlechat.go","lineNumber":216,"sourceCode":"\t\treturn errors.New(\"failed to construct Google Chat webhook POST request\")\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/json; charset=UTF-8\")\n\tclient := &http.Client{\n\t\tTimeout: webhook.Timeout,\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn errors.New(\"failed to POST Google Chat webhook\")\n\t}\n\tdefer resp.Body.Close()\n\n\tb, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn errors.New(\"failed to read Google Chat webhook response\")\n\t}\n\tif resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {\n\t\treturn errors.Errorf(\"failed to POST Google Chat webhook, status code: %d, response body: %s\", resp.StatusCode, b)\n\t}\n\n\treturn nil\n}\n\nfunc marshal(post MessagePayload) ([]byte, error) {\n\tvar body bytes.Buffer\n\tencoder := json.NewEncoder(&body)\n\tencoder.SetEscapeHTML(false)\n\tif err := encoder.Encode(post); err != nil {\n\t\treturn nil, err\n\t}\n\treturn bytes.TrimSpace(body.Bytes()), nil\n}\n","sourceCodeStart":198,"sourceCodeEnd":231,"githubUrl":"https://github.com/bytebase/bytebase/blob/1870550677fe08f0d2a78c07acd27541464eb945/backend/plugin/webhook/googlechat/googlechat.go#L198-L231","documentation":"This error is returned when the Google Chat webhook endpoint replies with a non-2xx HTTP status code. postMessage reads the whole response body and embeds both the status code and the raw body in the message, so the caller can see exactly what Google Chat rejected and why. It is a server-side rejection of the POST, not a transport failure.","triggerScenarios":"postMessage POSTs the marshaled message to context.URL via client.Do, gets a response with StatusCode < 200 or >= 300, and formats the status plus response body into this error.","commonSituations":"The webhook URL was revoked or mistyped (404), the Google Chat space webhook was deleted (403/404), an invalid or truncated webhook URL/key is configured, or Google rate-limits the space (429).","solutions":["Read the status code and body in the error to identify the rejection reason (404 = bad URL, 403 = revoked webhook, 429 = rate limited).","Re-create the Google Chat space webhook in the Chat UI and update the stored webhook URL, since deleted/rotated webhooks return 403/404.","Verify the configured URL matches the full https://chat.googleapis.com/v1/spaces/.../messages?key=...&token=... form.","If 429, reduce notification frequency or batch events before retrying with backoff."],"exampleFix":"// before\nURL: \"https://chat.googleapis.com/v1/spaces/OLD_SPACE_ID/messages?key=oldKey\"\n// returns 404 not found\n// after\nURL: \"https://chat.googleapis.com/v1/spaces/AAAA-newSpace/messages?key=newKey&token=newToken\"\n// returns 200 OK","handlingStrategy":"retry","validationCode":"// Before saving the webhook URL, ping it:\nfunc validateChatWebhook(u string) error {\n\treq, _ := http.NewRequest(http.MethodPost, u, bytes.NewBufferString(`{\"text\":\"test\"}`))\n\treq.Header.Set(\"Content-Type\", \"application/json; charset=UTF-8\")\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil { return err }\n\tdefer resp.Body.Close()\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf(\"webhook returned %d\", resp.StatusCode) }\n\treturn nil\n}","typeGuard":"func isWebhookStatusError(err error) (int, string, bool) {\n\tvar n, msg string\n\t_, scanErr := fmt.Sscanf(err.Error(), \"failed to POST Google Chat webhook, status code: %d\", new(int))\n\t_ = n; _ = msg; _ = scanErr\n\treturn 0, \"\", strings.Contains(err.Error(), \"status code:\")\n}","tryCatchPattern":"if err := googlechat.Post(ctx); err != nil {\n\tvar status int\n\tif n, _ := fmt.Sscanf(err.Error(), \"failed to POST Google Chat webhook, status code: %d\", &status); n == 1 {\n\t\tswitch {\n\t\tcase status == 429:\n\t\t\ttime.Sleep(backoff); retry()\n\t\tcase status == 404 || status == 403:\n\t\t\talertOwnerToRecreateWebhook(err)\n\t\t}\n\t}\n\treturn err\n}","preventionTips":["Validate the webhook URL with a test POST when saving integration settings","Treat 403/404 as 'webhook deleted' and alert the admin to re-create it","Retry only 5xx/429 with exponential backoff; never retry 4xx permanently","Monitor for recurring non-2xx to detect silently revoked webhooks"],"tags":["http","webhook","google-chat","api-error-response"],"backgroundTag":"http-error-response","analyzedSha":"1870550677fe08f0d2a78c07acd27541464eb945","analyzedAt":"2026-09-06T21:16:13.665Z","contentChangedAt":"2026-09-06T21:16:13.665Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}