{"record":{"id":"104e99c9c4973e63","repo":"bytebase/bytebase","slug":"s-104e99","errorCode":null,"errorMessage":"%s","messagePattern":"%s","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/plugin/webhook/lark/lark.go","lineNumber":219,"sourceCode":"\t}\n\n\tb, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"failed to read POST webhook response from %s\", context.URL)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn errors.Errorf(\"failed to POST webhook %s, status code: %d, response body: %s\", context.URL, resp.StatusCode, b)\n\t}\n\n\twebhookResponse := &WebhookResponse{}\n\tif err := json.Unmarshal(b, webhookResponse); err != nil {\n\t\treturn errors.Wrapf(err, \"malformed webhook response from %s\", context.URL)\n\t}\n\n\tif webhookResponse.Code != 0 {\n\t\treturn errors.Errorf(\"%s\", webhookResponse.Message)\n\t}\n\n\treturn nil\n}\n\nfunc getMessageCard(context webhook.Context) *WebhookCard {\n\tvar markdownBuf strings.Builder\n\n\tif context.Description != \"\" {\n\t\t_, _ = fmt.Fprintf(&markdownBuf, \"%s\\n\", context.Description)\n\t}\n\n\tfor _, meta := range context.GetMetaList() {\n\t\t_, _ = fmt.Fprintf(&markdownBuf, \"**%s**: %s\\n\", meta.Name, meta.Value)\n\t}\n\n\tif context.ActorName != \"\" {\n\t\t_, _ = fmt.Fprintf(&markdownBuf, \"**Actor**: %s (%s)\\n\", context.ActorName, context.ActorEmail)","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/bytebase/bytebase/blob/1870550677fe08f0d2a78c07acd27541464eb945/backend/plugin/webhook/lark/lark.go#L201-L237","documentation":"Lark's webhook endpoint returned 200 with parseable JSON, but the response Code field is non-zero, indicating Lark rejected the message. The error surfaces Lark's own Message field (e.g. 'key words not found' or rate-limit text). This is a Lark-side business error, not a transport failure.","triggerScenarios":"postMessage gets Code != 0 in the WebhookResponse — most commonly Lark's 'key words not found' security policy (message must contain a configured keyword), invalid sign (signature mismatch for signature-enabled bots), or exceeding frequency limits.","commonSituations":"Lark bot configured with a custom keyword but the Bytebase message doesn't contain it; signature verification enabled in Lark but Bytebase signs with a wrong secret or clock drift; sending too many notifications per second (Lark limits ~5/s per bot).","solutions":["Read the surfaced Message text — it names the exact Lark-side cause.","If the message is 'key words not found', either add the configured keyword to Bytebase's message or remove the keyword requirement in the Lark bot settings.","If the message indicates an invalid sign, re-check the webhook secret/token and ensure server clock sync (NTP).","If rate-limited, add throttling/queueing for webhook deliveries and retry with backoff.","Verify the bot still exists and is enabled in the Lark group; recreate the webhook if revoked."],"exampleFix":"// before: message may not contain the Lark bot keyword\n\"title\": \"Pipeline succeeded\"\n// after: include the configured keyword in the card text\n\"title\": \"[Bytebase] Pipeline succeeded\"","handlingStrategy":"validation","validationCode":"// ensure the configured keyword appears in the outgoing message text\nif larkKeyword != \"\" && !strings.Contains(cardText, larkKeyword) {\n    return fmt.Errorf(\"message must contain Lark bot keyword %q\", larkKeyword)\n}","typeGuard":"null","tryCatchPattern":"if err := postMessage(...); err != nil {\n    msg := strings.TrimPrefix(err.Error(), \"\")\n    switch {\n    case strings.Contains(msg, \"key words not found\"):\n        // fix keyword configuration\n    case strings.Contains(msg, \"sign\"):\n        // fix secret / clock sync\n    default:\n        // rate limit or other: retry with backoff\n    }\n}","preventionTips":["Configure the Lark bot keyword to match a fixed prefix Bytebase includes in every card.","If using signed webhooks, keep the secret in sync and run NTP on the server.","Throttle sends to stay under Lark's per-bot rate limit (~5/sec).","Keep the webhook active — recreate and update config if the bot is removed from the group."],"tags":["webhook","lark","api","http"],"backgroundTag":"api-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"}