{"record":{"id":"128e46dda5a86a4d","repo":"bytebase/bytebase","slug":"failed-to-get-token","errorCode":null,"errorMessage":"failed to get token","messagePattern":"failed to get token","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/plugin/webhook/dingtalk/app.go","lineNumber":57,"sourceCode":"\t}\n\tmobile, err := getDingTalkMobileFromPhone(phone)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"failed to parse phone number\")\n\t}\n\tid, err = p.getIDByPhone(ctx, mobile)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"failed to get user id by phone\")\n\t}\n\tif err := p.sendMessage(ctx, []string{id}, \"test\", \"test\"); err != nil {\n\t\treturn errors.Wrapf(err, \"failed to send test message\")\n\t}\n\treturn nil\n}\n\nfunc (p *provider) refreshToken(ctx context.Context) error {\n\ttoken, err := getTokenCached(ctx, p.c, p.id, p.secret)\n\tif err != nil {\n\t\treturn errors.Wrapf(err, \"failed to get token\")\n\t}\n\tp.token = token\n\treturn nil\n}\n\nvar userIDCache = func() *lru.Cache[string, string] {\n\tcache, err := lru.New[string, string](5000)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn cache\n}()\n\n// https://open.dingtalk.com/document/orgapp/query-users-by-phone-number\nfunc (p *provider) getIDByPhone(ctx context.Context, phone string) (string, error) {\n\tif id, ok := userIDCache.Get(phone); ok {\n\t\treturn id, nil\n\t}","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/bytebase/bytebase/blob/1870550677fe08f0d2a78c07acd27541464eb945/backend/plugin/webhook/dingtalk/app.go#L39-L75","documentation":"refreshToken wraps any error from getTokenCached (the DingTalk token endpoint or its cache path) with \"failed to get token\". The DingTalk provider needs an access token before calling any API; if obtaining/caching that token fails, this wrapper is raised. It indicates the provider cannot authenticate to DingTalk at all, so all downstream calls fail.","triggerScenarios":"Calling Validate, do, or posting a webhook issue when getTokenCached fails: wrong appKey/appSecret (p.id/p.secret), DingTalk token endpoint unreachable, or the cached-token fetch returning an API error.","commonSituations":"Misconfigured DingTalk app credentials in webhook settings; rotated/revoked app secret; corporate firewall blocking oapi.dingtalk.com / api.dingtalk.com; DingTalk-side outage or rate limiting of the token endpoint.","solutions":["Verify the DingTalk appKey (id) and appSecret configured on the webhook provider are correct and the app is enabled","Check network egress to https://oapi.dingtalk.com and https://api.dingtalk.com from the server","Inspect the wrapped cause (err chain) for the exact DingTalk errcode and fix accordingly","Retry after confirming DingTalk service status if it was transient"],"exampleFix":"// before\nprovider := dingtalk.New(ctx, httpClient, \"\", \"wrong-secret\")\n// after\nprovider := dingtalk.New(ctx, httpClient, \"ding-app-key\", \"correct-app-secret\")","handlingStrategy":"try-catch","validationCode":"if provider.ID == \"\" || provider.Secret == \"\" {\n\treturn errors.New(\"dingtalk appKey/appSecret must be set before validating webhook\")\n}","typeGuard":null,"tryCatchPattern":"if err := p.refreshToken(ctx); err != nil {\n\tvar dingErr *errors.Error\n\tif errors.As(err, &dingErr) {\n\t\tlog.Printf(\"dingtalk token error: %v\", dingErr)\n\t}\n\treturn fmt.Errorf(\"webhook validation aborted: token unavailable: %w\", err)\n}","preventionTips":["Store appKey/appSecret in a secret manager and rotate them together with the DingTalk console","Smoke-test token acquisition at startup or configuration save time","Monitor egress connectivity to *.dingtalk.com from deployment environments"],"tags":["dingtalk","authentication","webhook","network"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"1870550677fe08f0d2a78c07acd27541464eb945","analyzedAt":"2026-09-06T21:16:13.665Z","contentChangedAt":"2026-09-06T21:16:13.665Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}