yikart/AiToEarn · error

ChannelWebhookInvalidSignature

Error message

ChannelWebhookInvalidSignature

What it means

Instagram webhook provider rejects POST notifications whose X-Hub-Signature-256 header does not match an HMAC-SHA256 computed from the raw request body using the app's client secret. The provider responds 401 with a localized ChannelWebhookInvalidSignature message and drops the payload. This guards against spoofed or tampered Meta webhook events.

Source

Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/instagram/instagram-webhook.provider.ts:41

@Injectable()
export class InstagramWebhookProvider implements PlatformWebhookHandler {
  private readonly logger = new Logger(InstagramWebhookProvider.name)

  constructor(
    private readonly config: InstagramConfig,
    @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.verify(request)) {
      this.logger.warn({ platform: AccountType.Instagram }, 'Instagram webhook signature invalid')
      response.status(401).send(getCodeMessage(ResponseCode.ChannelWebhookInvalidSignature, undefined, getLocale()))
      return
    }
    const body = this.parseMetaBody(request)
    if (!body) {
      response.status(200).send('EVENT_RECEIVED')
      return
    }
    if (body.object && body.object !== InstagramWebhookObject.Instagram) {
      response.status(200).send('EVENT_RECEIVED')
      return
    }
    for (const entry of body.entry ?? []) {
      for (const change of entry.changes ?? []) {
        await this.applyInstagramChange(change)
      }
    }
    response.status(200).send('EVENT_RECEIVED')
  }

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Verify the configured client secret matches the Meta app that subscribed the webhook
  2. Ensure the framework preserves request.rawBody for this route (raw-body middleware before JSON parsing)
  3. Check that no intermediary (proxy, WAF) modifies the request body or strips the signature header
  4. Retry the webhook subscription from Meta App Dashboard to resend with a valid signature

Example fix

// before
 InstagramModule.forRoot({ clientSecret: process.env.META_APP_ID_SECRET_DEV })
// after
 InstagramModule.forRoot({ clientSecret: process.env.META_APP_SECRET }) // correct app secret for the subscribed app
Defensive patterns

Strategy: validation

Validate before calling

const sig = req.headers['x-hub-signature-256']
if (!sig?.startsWith('sha256=')) throw new Error('missing Instagram signature')
const expected = 'sha256=' + createHmac('sha256', process.env.META_APP_SECRET).update(rawBody).digest('hex')
if (!timingSafeEqual(Buffer.from(expected), Buffer.from(sig))) throw new Error('signature mismatch')

Type guard

function hasValidIgSignature(sig: unknown): sig is string {
  return typeof sig === 'string' && sig.startsWith('sha256=')
}

Prevention

When it happens

Trigger: POST to the Instagram webhook endpoint with a missing, malformed (not 'sha256=' prefixed), or mismatched x-hub-signature-256 header; or when config.clientSecret is unset so verification always fails.

Common situations: Wrong or rotated Meta app secret in env (INSTAGRAM_CLIENT_SECRET); a proxy/gateway re-encoding the body so the signature no longer matches raw bytes; missing rawBody due to body-parser ordering; traffic not actually from Meta (scanners probing the endpoint).

Related errors


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