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

  1. Decode the embedded JSON and match errcode (40001 wrong secret, 60020 IP allowlist, 40013 invalid corpid)
  2. Re-copy corpid + corpsecret for the exact same app from the WeChat Work admin console and update config
  3. Add the server's outbound IP to the app's 'trusted IP' allowlist
  4. 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

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


AI-assisted analysis of w7corp/easywechat@f0cf0a8b83 (2026-08-21). Data as JSON: /api/errors/878993306019b30e. Report an issue: GitHub.