JeffreySu/WeiXinMPSDK · error · WeixinWorkException
UN_REGISTER_ALERT
Error message
UN_REGISTER_ALERT
What it means
JsApiTicketContainer.GetTicketResult throws WeixinWorkException with the UN_REGISTER_ALERT message ("此AppId尚未注册,JsApiTicketContainer.Register完成注册(全局执行一次即可)!") when CheckRegistered(appKey) returns false — i.e. no ticket bag exists in cache for the appId+appSecret (+isAgentConfig) key. The Senparc containers require an explicit one-time Register call at application startup before any Get* method can be used. GetTicket (its caller) surfaces the same exception.
Solutions
- Call JsApiTicketContainer.Register(appId, appSecret, isAgentConfig) once at application startup (e.g. in Program.cs/Startup) before serving requests.
- Ensure the appId/appSecret/isAgentConfig passed to GetTicket exactly match the values used at Register.
- In multi-tenant scenarios, register each tenant's app when it is first seen (CheckRegistered then Register).
- If using an external/distributed cache, re-run registration on cache restarts or register in a background initializer that retries.
Example fix
// before
var ticket = JsApiTicketContainer.GetTicket(appId, appSecret, false); // throws if not registered
// after
if (!JsApiTicketContainer.CheckRegistered(appId, appSecret, false))
{
JsApiTicketContainer.Register(appId, appSecret, false);
}
var ticket = JsApiTicketContainer.GetTicket(appId, appSecret, false); Defensive patterns
Strategy: validation
Validate before calling
if (!JsApiTicketContainer.CheckRegistered(appId, appSecret, isAgentConfig))
{
JsApiTicketContainer.Register(appId, appSecret, isAgentConfig);
} Try / catch
try
{
var ticket = JsApiTicketContainer.GetTicket(appId, appSecret, isAgentConfig);
}
catch (WeixinWorkException ex) when (ex.Message.Contains("尚未注册"))
{
log.LogError(ex, "JS-SDK app {AppId} not registered with JsApiTicketContainer; register at startup.", appId);
JsApiTicketContainer.Register(appId, appSecret, isAgentConfig);
} Prevention
- Call Container registration for all apps in Program.cs/Startup before the app starts serving traffic.
- Keep a single registry of (appId, appSecret, isAgentConfig) tuples used by both Register and Get.
- Re-run registration when the cache backend restarts; don't register only in one-off scripts.
When it happens
Trigger: Calling JsApiTicketContainer.GetTicket/GetTicketResult before JsApiTicketContainer.Register(appId, appSecret, isAgentConfig) was executed; cache was flushed/expired and registration was not repeated; registering with a different appId/appSecret/isAgentConfig combination than the one queried.
Common situations: Forgot the startup registration after adding JS-SDK pages; multi-tenant apps where a new tenant's app is queried without registering it; distributed cache cleared (Redis restart) while registration only runs once at boot; appSecret changed so the key no longer matches.
Related errors
AI-assisted analysis of JeffreySu/WeiXinMPSDK@be573f6f94 (2026-09-12).
Data as JSON: /api/errors/3b65491de9e63bf4.
Report an issue: GitHub.
Appendix: source
Thrown at src/Senparc.Weixin.Work/Senparc.Weixin.Work/Containers/JsApiTicketContainer.cs:197
/// <returns></returns>
public static string GetTicket(string appId, string appSecret, bool isAgentConfig, bool getNewTicket = false)
{
return GetTicketResult(appId, appSecret, isAgentConfig, getNewTicket).ticket;
}
/// <summary>
/// 获取可用Ticket
/// </summary>
/// <param name="appId"></param>
/// <param name="getNewTicket">是否强制重新获取新的Ticket</param>
/// <param name="isAgentConfig">是否为“应用jsapi_ticket”,如果是某个特定应用,则输入 true(用于计算 agentConfig 的签名),否则为 false。参考:<see href="https://developer.work.weixin.qq.com/document/path/90506#14924"/></param>
/// <returns></returns>
public static JsApiTicketResult GetTicketResult(string appId, string appSecret, bool isAgentConfig, bool getNewTicket = false)
{
var appKey = AccessTokenContainer.BuildingKey(appId, appSecret,isAgentConfig);
if (!CheckRegistered(appKey))
{
throw new WeixinWorkException(UN_REGISTER_ALERT);
}
var jsApiTicketBag = TryGetItem(appKey);
using (Cache.BeginCacheLock(LockResourceName, appKey))
{
jsApiTicketBag = TryGetItem(appKey);//获锁后重新读取并二次检查过期状态
if (getNewTicket || jsApiTicketBag.ExpireTime <= SystemTime.Now)
{
//已过期,重新获取
jsApiTicketBag.JsApiTicketResult = CommonApi.GetTicket(jsApiTicketBag.CorpId, jsApiTicketBag.CorpSecret, isAgentConfig);
jsApiTicketBag.ExpireTime = ApiUtility.GetExpireTime(jsApiTicketBag.JsApiTicketResult.expires_in);
Update(jsApiTicketBag, null);//更新到缓存
}
}
return jsApiTicketBag.JsApiTicketResult;
}
///// <summary>View on GitHub (pinned to be573f6f94)