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 FacebookWebhookChallengeQuery

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Confirm FACEBOOK_APP_SECRET in the server env matches the Meta app that sends the webhook
  2. Ensure signature verification uses the exact raw body (request.isRaw / raw-body middleware), bypassing body-parser re-serialization
  3. Check that only the intended Meta app is subscribed to this webhook URL and the correct environment is deployed
  4. 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

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.