binarywang/WxJava · error · WxErrorException

通讯录同步secret未配置

Error message

通讯录同步secret未配置

What it means

Thrown (as checked WxErrorException) by getContactAccessToken() in the default WxCpServiceImpl when the contact-sync secret is null or blank. This is the base/default HTTP client implementation. The contact secret is a separate credential from the main corp secret, used exclusively for contact-sync APIs.

Source

Thrown at weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java:89

  }

  @Override
  public String getContactAccessToken(boolean forceRefresh) throws WxErrorException {
    final WxCpConfigStorage configStorage = getWxCpConfigStorage();
    if (!configStorage.isContactAccessTokenExpired() && !forceRefresh) {
      return configStorage.getContactAccessToken();
    }
    Lock lock = configStorage.getContactAccessTokenLock();
    lock.lock();
    try {
      // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷
      if (!configStorage.isContactAccessTokenExpired() && !forceRefresh) {
        return configStorage.getContactAccessToken();
      }
      // 使用通讯录同步secret获取access_token
      String contactSecret = configStorage.getContactSecret();
      if (contactSecret == null || contactSecret.trim().isEmpty()) {
        throw new WxErrorException("通讯录同步secret未配置");
      }
      String url = String.format(configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN),
        this.configStorage.getCorpId(), contactSecret);
      try {
        HttpGet httpGet = new HttpGet(url);
        if (getRequestHttpProxy() != null) {
          RequestConfig config = RequestConfig.custom().setProxy(getRequestHttpProxy()).build();
          httpGet.setConfig(config);
        }
        String resultContent = getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE);
        WxError error = WxError.fromJson(resultContent, WxType.CP);
        if (error.getErrorCode() != 0) {
          throw new WxErrorException(error);
        }

        WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
        configStorage.updateContactAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
      } catch (IOException e) {

View on GitHub (pinned to 1c43293a3c)

Solutions

  1. Call configStorage.setContactSecret("...") during application startup before any contact-sync API is used
  2. In Spring Boot starter, set wx.cp.contact-secret in application.yml or application.properties
  3. If using a custom config storage (e.g., Redis), ensure the contact secret is persisted and reloaded correctly
  4. Differentiate the contact-sync secret from the application secret — they are separate values in the WeChat admin console

Example fix

// before
WxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl();
config.setCorpId(corpId);
config.setCorpSecret(corpSecret);
// missing: config.setContactSecret(...)

// after
config.setContactSecret(System.getenv("WX_CP_CONTACT_SECRET"));
Defensive patterns

Strategy: validation

Validate before calling

// Validate contact secret at startup for default impl
String contactSecret = configStorage.getContactSecret();
if (StringUtils.isBlank(contactSecret)) {
  log.error("通讯录同步 secret 未配置,通讯录相关接口将不可用");
  // Optionally throw to fail-fast
}

Prevention

When it happens

Trigger: Any contact-sync API call (user/department/tag sync) through the default WxCpServiceImpl when configStorage.getContactSecret() returns null or empty.

Common situations: Config initialized with only corpId + corpSecret but no contactSecret; the contact secret env var is undefined; using a Redis-backed config storage that lost the contact secret after a flush; the WeChat Work admin console was reconfigured and the old secret is no longer valid (though this would cause a different error — this specific error is only about the value being absent).

Related errors


AI-assisted analysis of binarywang/WxJava@1c43293a3c (2026-08-14). Data as JSON: /api/errors/7f346f84d5ed886e. Report an issue: GitHub.