yikart/AiToEarn · warning
ChannelWebhookInvalidSignature
Error message
ChannelWebhookInvalidSignature
What it means
The Facebook webhook provider rejects incoming webhook payloads when verifyMetaSignature(request) fails, responding 401 with the localized ChannelWebhookInvalidSignature message. Meta signs payloads with X-Hub-Signature-256 (HMAC-SHA256 of the raw body using the app secret); a mismatch means the request did not genuinely come from Meta or was altered in transit.
Source
Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/facebook/facebook-webhook.provider.ts:39
@Injectable()
export class FacebookWebhookProvider implements PlatformWebhookHandler {
private readonly logger = new Logger(FacebookWebhookProvider.name)
constructor(
private readonly config: FacebookConfig,
@Optional() private readonly publishRecordRepo?: PublishRecordRepository,
@Optional() private readonly stateService?: PublishStateService,
) {}
async handle(request: Request, response: Response): Promise<void> {
if (request.method === 'GET') {
this.handleChallenge(request, response)
return
}
if (!this.verifyMetaSignature(request)) {
this.logger.warn({ platform: AccountType.Facebook }, 'Facebook webhook signature invalid')
response.status(401).send(getCodeMessage(ResponseCode.ChannelWebhookInvalidSignature, undefined, getLocale()))
return
}
const body = this.parseMetaBody(request)
for (const entry of body.entry ?? []) {
for (const change of entry.changes ?? []) {
await this.applyFacebookChange(change)
}
}
response.status(200).send('EVENT_RECEIVED')
}
private handleChallenge(request: Request, response: Response): void {
const {
'hub.mode': mode,
'hub.verify_token': verifyToken,
'hub.challenge': challenge,
} = request.query as FacebookWebhookChallengeQueryView on GitHub (pinned to d3aa8bea5b)
Solutions
- Confirm FACEBOOK_APP_SECRET in the server env matches the Meta app that sends the webhook
- Ensure signature verification uses the exact raw body (request.isRaw / raw-body middleware), bypassing body-parser re-serialization
- Check that only the intended Meta app is subscribed to this webhook URL and the correct environment is deployed
- Inspect the received X-Hub-Signature-256 header in logs and recompute HMAC locally to diagnose the mismatch
Example fix
// before
app.use(express.json()) // body parsed before webhook route -> raw bytes lost
// after
app.use('/webhooks/facebook', express.raw({ type: 'application/json' })) // verify against raw bytes, then parse Defensive patterns
Strategy: validation
Validate before calling
const expected = crypto.createHmac('sha256', appSecret).update(rawBody).digest('hex')
if (req.headers['x-hub-signature-256'] !== `sha256=${expected}`) return res.status(401).send('invalid signature') Type guard
function hasMetaSignature(req: Request, appSecret: string): boolean {
const header = req.headers['x-hub-signature-256'] as string | undefined
if (!header?.startsWith('sha256=')) return false
const expected = crypto.createHmac('sha256', appSecret).update(req.body as Buffer).digest('hex')
return crypto.timingSafeEqual(Buffer.from(header.slice(7)), Buffer.from(expected))
} Try / catch
app.post('/webhooks/facebook', express.raw({ type: 'application/json' }), (req, res) => {
if (!hasMetaSignature(req, process.env.FACEBOOK_APP_SECRET!)) {
return res.status(401).send('ChannelWebhookInvalidSignature')
}
const body = JSON.parse((req.body as Buffer).toString())
// process entries
}) Prevention
- Mount express.raw before JSON body parsing on the Meta webhook route
- Keep FACEBOOK_APP_SECRET per environment and aligned with the Meta app sending events
- Use one webhook URL per Meta app to avoid cross-app secret mismatches
- Log and alert on signature failures to detect config drift quickly
When it happens
Trigger: POST to the Facebook webhook endpoint where X-Hub-Signature-256 is absent, computed with a different app secret than the server's FACEBOOK_APP_SECRET, or the raw body bytes differ from what was signed (proxy re-serialization, parsed-then-restringified body).
Common situations: Meta app secret rotated/changed per environment without updating server env; reverse proxy (nginx/CDN) modifying the body; multiple Meta apps sharing one webhook URL; forged probing requests; webhook receiving events for a different app than configured.
Related errors
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/b5e9564cb59b3f05.
Report an issue: GitHub.