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
- Verify the configured client secret matches the Meta app that subscribed the webhook
- Ensure the framework preserves request.rawBody for this route (raw-body middleware before JSON parsing)
- Check that no intermediary (proxy, WAF) modifies the request body or strips the signature header
- 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
- Keep META_APP_SECRET in sync with the Meta app sending webhooks
- Always capture rawBody before JSON parsing
- Never proxy webhook bodies through re-serializing middleware
- Use constant-time comparison for signatures
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.