{"record":{"id":"730e6863662bc519","repo":"yikart/AiToEarn","slug":"tiktok-webhook-signature-invalid","errorCode":null,"errorMessage":"TikTok webhook signature invalid","messagePattern":"TikTok webhook signature invalid","errorType":"http","errorClass":null,"httpStatus":401,"severity":"warning","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/tiktok/tiktok-webhook.provider.ts","lineNumber":55,"sourceCode":"\n@Injectable()\nexport class TikTokWebhookProvider implements PlatformWebhookHandler {\n  private readonly logger = new Logger(TikTokWebhookProvider.name)\n\n  constructor(\n    private readonly config: TiktokConfig,\n    @Optional() private readonly publishRecordRepo?: PublishRecordRepository,\n    @Optional() private readonly stateService?: PublishStateService,\n  ) {}\n\n  async handle(request: Request, response: Response): Promise<void> {\n    if (request.method === 'GET') {\n      const query = request.query as TikTokWebhookChallengeQuery\n      response.status(query.challenge ? 200 : 404).send(query.challenge ?? '')\n      return\n    }\n    if (!this.verify(request)) {\n      this.logger.warn({ platform: AccountType.TikTok }, 'TikTok webhook signature invalid')\n      response.status(401).json({ status: 'invalid_signature' })\n      return\n    }\n    const body = this.parseTikTokBody(request)\n    if (!body) {\n      response.status(200).json({ status: 'ok' })\n      return\n    }\n    await this.applyTikTokPublishResult(body)\n    response.status(200).json({ status: 'ok' })\n  }\n\n  private verify(request: RawBodyRequest): boolean {\n    const rawBody = request.rawBody\n    const signatureHeader = this.getHeader(request, 'tiktok-signature')\n      ?? this.getHeader(request, 'x-tiktok-signature')\n      ?? this.getHeader(request, 'x-tt-signature')\n    if (!signatureHeader || !rawBody || !this.config.clientSecret) {","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/tiktok/tiktok-webhook.provider.ts#L37-L73","documentation":"The TikTok webhook provider rejects POST deliveries whose verify(request) check fails — TikTok signs payloads and the provider validates the signature against the configured secret. On failure it logs a warning and returns HTTP 401 with { status: 'invalid_signature' } instead of processing the event. (No AppException code is attached; the response body is the contract.)","triggerScenarios":"A POST to the TikTok webhook endpoint where the computed HMAC of the raw body does not match the signature header: wrong secret configured, body re-encoded by middleware, missing header, or a non-TikTok caller.","commonSituations":"TikTok app secret rotated without updating backend env; Express body-parser consumed the stream before signature verification; testing the endpoint manually; payload passed through a transforming proxy.","solutions":["Confirm the TikTok client secret in config matches the app registered for webhooks","Verify signature over the raw request body bytes (rawBody), not re-serialized JSON","Check the exact signature header name TikTok sends for your webhook event type and that verify() reads it","Exclude the webhook route from body-rewriting middleware/proxies"],"exampleFix":"// before\nconst body = JSON.stringify(request.body)\nconst ok = verifySignature(body, request.headers['signature'], secret)\n// after\nconst raw = request.rawBody // captured pre-parse\nconst ok = raw ? verifySignature(raw, request.headers['signature'], secret) : false","handlingStrategy":"validation","validationCode":"const expected = crypto.createHmac('sha256', process.env.TIKTOK_CLIENT_SECRET!).update(req.rawBody).digest('hex')\nif (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(String(req.headers['signature'] ?? '')))) {\n  return res.status(401).json({ status: 'invalid_signature' })\n}","typeGuard":"function hasValidTikTokSignature(req: Request): boolean {\n  const sig = req.headers['signature']\n  return typeof sig === 'string' && Buffer.isBuffer((req as any).rawBody) && verifyHmac((req as any).rawBody, sig, secret)\n}","tryCatchPattern":"if (!hasValidTikTokSignature(req)) {\n  return res.status(401).json({ status: 'invalid_signature' })\n}","preventionTips":["Verify over rawBody captured before body parsers","Keep the TikTok app secret synced with the developer portal","Monitor invalid_signature responses for spikes indicating secret or proxy changes"],"tags":["webhook","signature-verification","tiktok","security"],"backgroundTag":"webhook-signature-invalid","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}