w7corp/easywechat · error · HttpException
Failed to get access_token: %s
Error message
Failed to get access_token: %s
What it means
Thrown by Work/AccessToken::refresh() when GET /cgi-bin/gettoken answers without access_token. HTTP transport worked, but WeChat rejected the credential pair — the body (with errcode) is embedded in the message. This is the base token for every self-built WeChat Work app API call.
Source
Thrown at src/Work/AccessToken.php:85
public function toQuery(): array
{
return ['access_token' => $this->getToken()];
}
/**
* @throws HttpException
*/
public function refresh(): string
{
$response = $this->httpClient->request('GET', '/cgi-bin/gettoken', [
'query' => [
'corpid' => $this->corpId,
'corpsecret' => $this->secret,
],
])->toArray(false);
if (empty($response['access_token'])) {
throw new HttpException('Failed to get access_token: '.json_encode($response, JSON_UNESCAPED_UNICODE));
}
$this->cache->set($this->getKey(), $response['access_token'], intval($response['expires_in']));
return $response['access_token'];
}
}
View on GitHub (pinned to f0cf0a8b83)
Solutions
- Decode the embedded JSON and match errcode (40001 wrong secret, 60020 IP allowlist, 40013 invalid corpid)
- Re-copy corpid + corpsecret for the exact same app from the WeChat Work admin console and update config
- Add the server's outbound IP to the app's 'trusted IP' allowlist
- Clear the cached token key so refresh() re-requests after fixing
Example fix
// before
$config = ['corp_id' => 'ww1234', 'secret' => 'secret-of-another-app'];
// after
$config = [
'corp_id' => 'ww1234',
'secret' => 'xxxxxxxx', // secret of the SAME app, re-copied
// ...
];
// plus: whitelist outbound IP in app settings Defensive patterns
Strategy: try-catch
Validate before calling
if (empty($config['corp_id']) || empty($config['secret'])) {
throw new \InvalidArgumentException('Work corp_id/secret required');
} Try / catch
use EasyWeChat\Kernel\Exceptions\HttpException;
try {
$work->access_token->getToken();
} catch (HttpException $e) {
$body = json_decode(strstr($e->getMessage(), '{'), true);
match ($body['errcode'] ?? 0) {
40001, 40013 => report('invalid corp credentials'),
60020 => report('IP not in allowlist'),
default => report('gettoken failed: '.$e->getMessage()),
};
} Prevention
- Validate corpid/secret presence and format (secret starts with - typically long string) at boot
- Automate allowlist updates when egress IPs change
- Rotate secrets through config and clear token cache atomically
When it happens
Trigger: First API call after cache expiry for any Work API (contacts, messages, checkin...). Produced by wrong corpid/corpsecret pair, secret from a different agent (40001/40013), or the server IP not in the app's IP allowlist (60020).
Common situations: Using the corp-level secret with an agent's corpid or vice versa; secret rotated in console but stale in config; moving from test to prod env with mismatched values; new egress IP (k8s cluster, NAT change) not whitelisted; copying a trailing space into the secret.
Related errors
- Failed to get stable access_token: %s
- Failed to get access_token: %s
- Failed to get component_access_token: %s
- Failed to get access_token: %s
- Failed to get jssdk ticket: %s
AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21).
Data as JSON: /api/errors/878993306019b30e.
Report an issue: GitHub.