{"record":{"id":"a9de781f19adb79a","repo":"w7corp/easywechat","slug":"failed-to-get-access-token-s","errorCode":null,"errorMessage":"Failed to get access_token: %s","messagePattern":"Failed to get access_token: (.+?)","errorType":"exception","errorClass":"HttpException","httpStatus":null,"severity":"critical","filePath":"src/OfficialAccount/AccessToken.php","lineNumber":123,"sourceCode":"    /**\n     * @throws HttpException\n     */\n    public function getAccessToken(): string\n    {\n        $response = $this->httpClient->request(\n            'GET',\n            'cgi-bin/token',\n            [\n                'query' => [\n                    'grant_type' => 'client_credential',\n                    'appid' => $this->appId,\n                    'secret' => $this->secret,\n                ],\n            ]\n        )->toArray(false);\n\n        if (empty($response['access_token'])) {\n            throw new HttpException('Failed to get access_token: '.json_encode($response, JSON_UNESCAPED_UNICODE));\n        }\n\n        $this->cache->set($this->getKey(), $response['access_token'], intval($response['expires_in']));\n\n        return $response['access_token'];\n    }\n}\n","sourceCodeStart":105,"sourceCodeEnd":131,"githubUrl":"https://github.com/w7corp/easywechat/blob/f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8/src/OfficialAccount/AccessToken.php#L105-L131","documentation":"OfficialAccount\\AccessToken::getAccessToken() is the classic token path: it GETs cgi-bin/token with grant_type=client_credential and the appid/secret, then throws HttpException with the raw JSON when no access_token comes back. WeChat rejected the client credentials or the calling IP. Every authenticated API call funnels through this token, so this failure blocks the whole account.","triggerScenarios":"Wrong appid or secret in config; server egress IP missing from the MP console IP whitelist (errcode 40164); secret rotated in the console but not in env; daily token quota/IP changes after infrastructure moves.","commonSituations":"Works locally but fails in production (different egress IP); credential drift between environments; containerized deployments with NAT'd IPs nobody whitelisted; env var typo (WECHAT_SECRET vs WECHAT_OFFICIAL_ACCOUNT_SECRET).","solutions":["json_decode the embedded JSON and act on errcode (40125/41004 → secret, 40013 → appid, 40164 → IP whitelist)","Whitelist the production egress IP in MP console → 基本配置 → IP名单","Fix the env/config values and clear the app config cache, then retry","Confirm you are not mixing the appid of one account with the secret of another"],"exampleFix":"// before: env key typo → empty secret sent to WeChat\n$config = ['app_id' => env('WECHAT_APP_ID'), 'secret' => env('WECHAT_SECRET')];\n\n// after\n$config = ['app_id' => env('WECHAT_APP_ID'), 'secret' => env('WECHAT_OFFICIAL_ACCOUNT_SECRET')];","handlingStrategy":"try-catch","validationCode":"if (blank($app->getConfig()->get('app_id')) || blank($app->getConfig()->get('secret'))) {\n    throw new \\RuntimeException('OfficialAccount app_id/secret must be set before token requests.');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $token = $accessToken->getAccessToken();\n} catch (\\EasyWeChat\\Kernel\\Exceptions\\HttpException $e) {\n    $payload = json_decode(strstr($e->getMessage(), '{') ?: '[]', true) ?: [];\n    // 40164 → whitelist IP; 40125/41004 → secret; 40013 → appid. Map each to a specific alert.\n    report($e);\n}","preventionTips":["Resolve the egress IP for each deployment target and whitelist it before go-live","Name env keys explicitly per account to avoid cross-env secret drift","Smoke-test token retrieval in a deploy check so credential failures surface at deploy time, not at runtime"],"tags":["php","easywechat","wechat","official-account","access-token","credentials","ip-whitelist"],"backgroundTag":"access-token-request-failed","analyzedSha":"f0cf0a8b8361417ed683b8246d0ecbaf0aafcaa8","analyzedAt":"2026-08-21T05:29:19.565Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}