{"record":{"id":"dd9792bca5acf767","repo":"yikart/AiToEarn","slug":"channelwebhookinvalidsignature-dd9792","errorCode":null,"errorMessage":"ChannelWebhookInvalidSignature","messagePattern":"ChannelWebhookInvalidSignature","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/wechat/wechat-official/wechat-official-webhook.provider.ts","lineNumber":38,"sourceCode":"  Encrypt?: string\n}\n\n@Injectable()\nexport class WeChatOfficialWebhookProvider implements PlatformWebhookHandler {\n  private readonly logger = new Logger(WeChatOfficialWebhookProvider.name)\n  private readonly xmlParser = new XMLParser({ ignoreAttributes: false })\n\n  constructor(private readonly config: WechatOfficialConfig) {}\n\n  async handle(request: Request, response: Response): Promise<void> {\n    if (request.method === 'GET') {\n      this.handleChallenge(request, response)\n      return\n    }\n\n    if (!this.verify(request)) {\n      this.logger.warn({ platform: AccountType.WeChatOfficial }, 'WeChat Official webhook signature invalid')\n      response.status(401).send(getCodeMessage(ResponseCode.ChannelWebhookInvalidSignature, undefined, getLocale()))\n      return\n    }\n\n    const event = this.parseWechatMessage(request)\n    this.logger.log({\n      platform: AccountType.WeChatOfficial,\n      event: event.event,\n      messageType: event.messageType,\n    }, 'WeChat Official webhook received')\n    response.status(200).send('success')\n  }\n\n  private handleChallenge(request: Request, response: Response): void {\n    const signature = this.getQuery(request, 'signature')\n    const timestamp = this.getQuery(request, 'timestamp')\n    const nonce = this.getQuery(request, 'nonce')\n    const echostr = this.getQuery(request, 'echostr')\n","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/wechat/wechat-official/wechat-official-webhook.provider.ts#L20-L56","documentation":"WeChat Official Account webhook provider verifies either the signature (plain mode) or msg_signature (encrypted mode) query parameter against a SHA-1 hash of token/timestamp/nonce (plus echostr/encrypt body). On failure it responds 401 with ChannelWebhookInvalidSignature and ignores the message.","triggerScenarios":"GET/POST to the WeChat Official webhook endpoint where the signature/msg_signature query param is missing or does not match the hash computed from config token, timestamp, and nonce.","commonSituations":"Server Token in WeChat MP console differs from configured webhook token; app switched between plain and encodingAESKey modes so the wrong signature field is checked; URL accessed directly by testers without WeChat's query params.","solutions":["Ensure the Token configured in the WeChat Official Account console exactly matches the provider's token config","Match the signature mode: check msg_signature when using encrypted (safe) mode, signature in plain mode","Verify timestamp/nonce from the query are included in the hash computation unchanged","Test the URL with WeChat's exact query parameters rather than by hand"],"exampleFix":"// before\nconst signature = this.getQuery(request, 'signature') // encrypted mode sends msg_signature only\n// after\nconst signature = this.getQuery(request, 'signature') ?? this.getQuery(request, 'msg_signature')","handlingStrategy":"validation","validationCode":"const p = req.query\nconst sig = (p.signature ?? p.msg_signature) as string | undefined\nif (!sig || !p.timestamp || !p.nonce || !process.env.WECHAT_OFFICIAL_TOKEN) throw new Error('missing WeChat signature params')\nconst hash = createHash('sha1').update([process.env.WECHAT_OFFICIAL_TOKEN, p.timestamp, p.nonce].sort().join('')).digest('hex')\nif (hash !== sig) throw new Error('WeChat signature mismatch')","typeGuard":"function hasWechatParams(q: Record<string, unknown>): q is Record<string, string> {\n  return typeof q.signature === 'string' || typeof q.msg_signature === 'string'\n}","tryCatchPattern":null,"preventionTips":["Keep the WeChat console Token identical to server config","Handle both plain (signature) and encrypted (msg_signature) modes","Verify timestamp/nonce come straight from the query string"],"tags":["webhook","signature","wechat","security"],"backgroundTag":"webhook-signature-verification-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}