yikart/AiToEarn · error
ChannelWebhookInvalidVerifyToken
Error message
ChannelWebhookInvalidVerifyToken
What it means
YouTube (WebSub) webhook provider validates hub.mode=subscribe challenges by comparing hub.verify_token to config.webhookVerifyToken. On mismatch it responds 403 with ChannelWebhookInvalidVerifyToken, and Google's WebSub hub treats the subscription as failed.
Source
Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/youtube/youtube-webhook.provider.ts:71
response.status(204).send()
}
private handleChallenge(request: Request, response: Response): void {
const mode = this.getQuery(request, 'hub.mode')
const challenge = this.getQuery(request, 'hub.challenge')
const verifyToken = this.getQuery(request, 'hub.verify_token')
if (
mode
&& challenge
&& (!this.config.webhookVerifyToken || verifyToken === this.config.webhookVerifyToken)
) {
response.status(200).send(challenge)
return
}
this.logger.warn({ platform: AccountType.YouTube, mode }, 'YouTube webhook challenge rejected')
response.status(403).send(getCodeMessage(ResponseCode.ChannelWebhookInvalidVerifyToken, undefined, getLocale()))
}
private parseYoutubeNotification(request: RawBodyRequest): YoutubeVideoNotification {
const xml = request.rawBody?.toString('utf8') ?? (typeof request.body === 'string' ? request.body : '')
const raw = this.xmlParser.parse(xml) as YoutubeAtomFeed
const entry = raw.feed?.entry
const videoId = entry?.['yt:videoId'] ?? entry?.id?.replace(/^yt:video:/, '')
return {
videoId,
permalink: this.pickYoutubeLink(entry?.link) ?? (videoId ? `https://www.youtube.com/watch?v=${videoId}` : undefined),
raw,
}
}
private async applyYoutubeVideoNotification(notification: YoutubeVideoNotification): Promise<void> {
if (!notification.videoId || !this.publishRecordRepo || !this.stateService) {
this.logger.warn({ platform: AccountType.YouTube, platformWorkId: notification.videoId }, 'YouTube webhook event cannot be matched')View on GitHub (pinned to d3aa8bea5b)
Solutions
- Re-register the WebSub subscription with the verify token currently configured on the server
- Make the configured webhookVerifyToken match the token used at subscribe time and keep it stable across deploys
- Confirm the callback URL is publicly reachable and unchanged
- Persist subscriptions so renewals use the same token
Example fix
// before // token rotated on server; old subscriptions still use old token // after // re-subscribe: POST to hub with hub.verify_token=<current configured token>
Defensive patterns
Strategy: validation
Validate before calling
const { 'hub.mode': mode, 'hub.verify_token': token, 'hub.challenge': challenge } = req.query as Record<string, string>
if (mode === 'subscribe' && token !== process.env.YOUTUBE_VERIFY_TOKEN) throw new Error('WebSub verify token mismatch') Type guard
function isWebSubChallenge(q: Record<string, unknown>): q is { 'hub.mode': string; 'hub.verify_token': string; 'hub.challenge': string } {
return q['hub.mode'] === 'subscribe' && typeof q['hub.verify_token'] === 'string' && typeof q['hub.challenge'] === 'string'
} Prevention
- Keep the verify token constant for the life of subscriptions
- Re-subscribe after changing the token
- Confirm callback URL is stable and publicly reachable
When it happens
Trigger: GET WebSub subscription request where hub.verify_token is absent or differs from the configured token, during channel subscription or renewal via the YouTube hub.
Common situations: Verify token changed on the server while Google retries with the old token; subscription callback URL registered with a different token; token lost across deployments/environments.
Related errors
- ChannelWebhookInvalidVerifyToken
- InvalidAiTaskId
- InvalidAiTaskId
- ChannelWebhookNotSupported
- No access token available, user needs to authorize
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/9bcb3efb065d4434.
Report an issue: GitHub.