{"record":{"id":"41d7afe7d5866903","repo":"yikart/AiToEarn","slug":"channelwebhookinvalidsignature-41d7af","errorCode":null,"errorMessage":"ChannelWebhookInvalidSignature","messagePattern":"ChannelWebhookInvalidSignature","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/linkedin/linkedin-webhook.provider.ts","lineNumber":27,"sourceCode":"\ninterface LinkedInWebhookChallengeQuery {\n  challengeCode?: string\n}\n\n@Injectable()\nexport class LinkedInWebhookProvider implements PlatformWebhookHandler {\n  private readonly logger = new Logger(LinkedInWebhookProvider.name)\n\n  constructor(private readonly config: LinkedinConfig) {}\n\n  async handle(request: Request, response: Response): Promise<void> {\n    if (request.method === 'GET') {\n      this.handleChallenge(request, response)\n      return\n    }\n    if (!this.verify(request)) {\n      this.logger.warn({ platform: AccountType.LinkedIn }, 'LinkedIn webhook signature invalid')\n      response.status(401).send(getCodeMessage(ResponseCode.ChannelWebhookInvalidSignature, undefined, getLocale()))\n      return\n    }\n    const payload: LinkedInWebhookPayload = LinkedInWebhookPayloadSchema.parse(request.body)\n    this.logger.log({\n      platform: AccountType.LinkedIn,\n      eventCount: payload.events?.length ?? 0,\n    }, 'LinkedIn webhook events received')\n    response.status(200).json({ status: 'ok' })\n  }\n\n  private handleChallenge(request: Request, response: Response): void {\n    const query = request.query as LinkedInWebhookChallengeQuery\n    if (!query.challengeCode) {\n      response.status(400).send(getCodeMessage(ResponseCode.ChannelWebhookChallengeCodeMissing, undefined, getLocale()))\n      return\n    }\n\n    const secret = this.config.webhookSecret || this.config.clientSecret","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/linkedin/linkedin-webhook.provider.ts#L9-L45","documentation":"LinkedIn webhook provider rejects POSTs whose X-Hub-Signature-256 header does not match the HMAC-SHA256 of the raw body computed with the webhook secret. It logs a warning for AccountType.LinkedIn and responds 401 with a localized ChannelWebhookInvalidSignature message.","triggerScenarios":"POST to the LinkedIn webhook endpoint with missing/malformed/mismatched signature header, missing rawBody, or unset clientSecret/webhookSecret so verification cannot pass.","commonSituations":"LinkedIn application webhook secret not configured or rotated; body transformed by middleware before HMAC check; unsigned probes hitting the public URL; event payload forwarded through an intermediary that re-serializes JSON.","solutions":["Configure the correct LinkedIn webhook secret (webhookSecret or clientSecret) for the app","Ensure rawBody capture middleware runs before JSON parsing on this route","Confirm no proxy rewrites the body or drops the signature header","Re-register/refresh the webhook subscription in LinkedIn's developer portal"],"exampleFix":"// before\nif (!this.config.clientSecret) throw new Error('missing') // verify silently fails downstream\n// after\nif (!this.config.webhookSecret && !this.config.clientSecret) {\n  this.logger.error('LinkedIn webhook secret not configured')\n}","handlingStrategy":"validation","validationCode":"const sig = req.headers['x-hub-signature-256']\nif (!sig?.startsWith('sha256=') || !rawBody || !process.env.LINKEDIN_WEBHOOK_SECRET) throw new Error('invalid LinkedIn signature')\nconst expected = createHmac('sha256', process.env.LINKEDIN_WEBHOOK_SECRET).update(rawBody).digest('hex')\nif (!timingSafeEqual(Buffer.from(expected), Buffer.from(sig.slice(7)))) throw new Error('signature mismatch')","typeGuard":"function hasLinkedInSignature(sig: unknown): sig is string {\n  return typeof sig === 'string' && sig.startsWith('sha256=')\n}","tryCatchPattern":null,"preventionTips":["Configure webhookSecret (or clientSecret) before enabling the endpoint","Preserve raw bytes of the body for HMAC","Monitor 401 rates to detect secret rotation mismatches early"],"tags":["webhook","signature","linkedin","security"],"backgroundTag":"webhook-signature-verification-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}