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