yikart/AiToEarn · error

ChannelWebhookInvalidSignature

Error message

ChannelWebhookInvalidSignature

What it means

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.

Source

Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/wechat/wechat-official/wechat-official-webhook.provider.ts:38

  Encrypt?: string
}

@Injectable()
export class WeChatOfficialWebhookProvider implements PlatformWebhookHandler {
  private readonly logger = new Logger(WeChatOfficialWebhookProvider.name)
  private readonly xmlParser = new XMLParser({ ignoreAttributes: false })

  constructor(private readonly config: WechatOfficialConfig) {}

  async handle(request: Request, response: Response): Promise<void> {
    if (request.method === 'GET') {
      this.handleChallenge(request, response)
      return
    }

    if (!this.verify(request)) {
      this.logger.warn({ platform: AccountType.WeChatOfficial }, 'WeChat Official webhook signature invalid')
      response.status(401).send(getCodeMessage(ResponseCode.ChannelWebhookInvalidSignature, undefined, getLocale()))
      return
    }

    const event = this.parseWechatMessage(request)
    this.logger.log({
      platform: AccountType.WeChatOfficial,
      event: event.event,
      messageType: event.messageType,
    }, 'WeChat Official webhook received')
    response.status(200).send('success')
  }

  private handleChallenge(request: Request, response: Response): void {
    const signature = this.getQuery(request, 'signature')
    const timestamp = this.getQuery(request, 'timestamp')
    const nonce = this.getQuery(request, 'nonce')
    const echostr = this.getQuery(request, 'echostr')

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Ensure the Token configured in the WeChat Official Account console exactly matches the provider's token config
  2. Match the signature mode: check msg_signature when using encrypted (safe) mode, signature in plain mode
  3. Verify timestamp/nonce from the query are included in the hash computation unchanged
  4. Test the URL with WeChat's exact query parameters rather than by hand

Example fix

// before
const signature = this.getQuery(request, 'signature') // encrypted mode sends msg_signature only
// after
const signature = this.getQuery(request, 'signature') ?? this.getQuery(request, 'msg_signature')
Defensive patterns

Strategy: validation

Validate before calling

const p = req.query
const sig = (p.signature ?? p.msg_signature) as string | undefined
if (!sig || !p.timestamp || !p.nonce || !process.env.WECHAT_OFFICIAL_TOKEN) throw new Error('missing WeChat signature params')
const hash = createHash('sha1').update([process.env.WECHAT_OFFICIAL_TOKEN, p.timestamp, p.nonce].sort().join('')).digest('hex')
if (hash !== sig) throw new Error('WeChat signature mismatch')

Type guard

function hasWechatParams(q: Record<string, unknown>): q is Record<string, string> {
  return typeof q.signature === 'string' || typeof q.msg_signature === 'string'
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/dd9792bca5acf767. Report an issue: GitHub.