{"record":{"id":"0f9982027f6efdec","repo":"yikart/AiToEarn","slug":"1-0f9982","errorCode":"-1","errorMessage":"ChannelWebhookInvalidSignature","messagePattern":"ChannelWebhookInvalidSignature","errorType":"http","errorClass":null,"httpStatus":401,"severity":"warning","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-webhook.provider.ts","lineNumber":25,"sourceCode":"import { PublishRecordRepository } from '@yikart/mongodb'\nimport { PublishStateService } from '../../publish/tasks/publish-state.service'\nimport { DouyinConfig } from './douyin.config'\nimport { buildDouyinVideoWorkLink, DouyinWebhookBodySchema, DouyinWebhookEvent } from './douyin.interface'\n\n@Injectable()\nexport class DouyinWebhookProvider implements PlatformWebhookHandler {\n  private readonly logger = new Logger(DouyinWebhookProvider.name)\n\n  constructor(\n    private readonly config: DouyinConfig,\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 (!this.verify(request)) {\n      this.logger.warn({ platform: AccountType.Douyin }, 'Douyin webhook signature invalid')\n      response.status(401).json({ code: -1, message: getCodeMessage(ResponseCode.ChannelWebhookInvalidSignature, undefined, getLocale()) })\n      return\n    }\n\n    const body = this.parseWebhookBody(request.body)\n    if (!body) {\n      response.status(200).json({ code: 0, message: 'ok' })\n      return\n    }\n\n    if (body.event === DouyinWebhookEvent.VerifyWebhook) {\n      response.status(200).json({ challenge: body.content.challenge })\n      return\n    }\n\n    if (body.event === DouyinWebhookEvent.CreateVideo) {\n      await this.applyCreateVideoResult(body)\n      response.status(200).json({ code: 0, message: 'ok' })\n      return","sourceCodeStart":7,"sourceCodeEnd":43,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-webhook.provider.ts#L7-L43","documentation":"The Douyin webhook provider rejects incoming webhook requests whose verify(request) check fails, responding 401 with code -1 and message ChannelWebhookInvalidSignature. The Douyin-signed payload did not match the configured secret, so the body is never parsed and no publish state is updated.","triggerScenarios":"POST to the Douyin webhook endpoint with a missing/incorrect signature header, a secret mismatch between Douyin's app config and the server env, or the body altered in transit before verification.","commonSituations":"Secret rotated in Douyin open-platform console without updating server config; gateway/proxy recompressing or re-encoding the payload; wrong environment deployment receiving the callback; attackers probing the endpoint.","solutions":["Verify the server's Douyin webhook secret matches the one configured in the Douyin open platform","Ensure the raw request body is passed unchanged through any reverse proxy to the handler","Confirm the request is hitting the correct environment deployment (cn vs intl secrets)","Rotate and re-register the webhook secret on both sides if compromised or drifted"],"exampleFix":"// before\nif (!this.verify(request)) {\n  this.logger.warn({ platform: AccountType.Douyin }, 'Douyin webhook signature invalid')\n// after\nif (!this.verify(request)) {\n  this.logger.warn({ platform: AccountType.Douyin, headers: request.headers['x-signature'] ? 'present' : 'missing' }, 'Douyin webhook signature invalid')","handlingStrategy":"validation","validationCode":"const expected = crypto.createHmac('sha256', douyinSecret).update(rawBody).digest('hex')\nif (req.headers['x-signature'] !== expected) return res.status(401).json({ code: -1 })","typeGuard":"function isDouyinSigned(req: Request, secret: string): boolean {\n  const sig = req.headers['x-signature'] as string | undefined\n  return !!sig && crypto.timingSafeEqual(Buffer.from(sig), crypto.createHmac('sha256', secret).update((req.body as Buffer)).digest())\n}","tryCatchPattern":"app.post('/webhooks/douyin', express.raw({ type: 'application/json' }), (req, res) => {\n  if (!isDouyinSigned(req, process.env.DOUYIN_WEBHOOK_SECRET!)) {\n    return res.status(401).json({ code: -1, message: 'ChannelWebhookInvalidSignature' })\n  }\n  // parse and process payload\n})","preventionTips":["Keep the Douyin open-platform secret and server env in sync; rotate together","Use express.raw for the webhook route so the signed bytes are verified exactly","Deploy per-environment secrets and confirm the callback URL points at the right deployment","Monitor 401 rates on the webhook endpoint as an alert signal"],"tags":["webhook","signature","security","douyin"],"backgroundTag":"webhook-signature-verification-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}