yikart/AiToEarn · warning

TikTok webhook signature invalid

Error message

TikTok webhook signature invalid

What it means

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.)

Source

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

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

  constructor(
    private readonly config: TiktokConfig,
    @Optional() private readonly publishRecordRepo?: PublishRecordRepository,
    @Optional() private readonly stateService?: PublishStateService,
  ) {}

  async handle(request: Request, response: Response): Promise<void> {
    if (request.method === 'GET') {
      const query = request.query as TikTokWebhookChallengeQuery
      response.status(query.challenge ? 200 : 404).send(query.challenge ?? '')
      return
    }
    if (!this.verify(request)) {
      this.logger.warn({ platform: AccountType.TikTok }, 'TikTok webhook signature invalid')
      response.status(401).json({ status: 'invalid_signature' })
      return
    }
    const body = this.parseTikTokBody(request)
    if (!body) {
      response.status(200).json({ status: 'ok' })
      return
    }
    await this.applyTikTokPublishResult(body)
    response.status(200).json({ status: 'ok' })
  }

  private verify(request: RawBodyRequest): boolean {
    const rawBody = request.rawBody
    const signatureHeader = this.getHeader(request, 'tiktok-signature')
      ?? this.getHeader(request, 'x-tiktok-signature')
      ?? this.getHeader(request, 'x-tt-signature')
    if (!signatureHeader || !rawBody || !this.config.clientSecret) {

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Confirm the TikTok client secret in config matches the app registered for webhooks
  2. Verify signature over the raw request body bytes (rawBody), not re-serialized JSON
  3. Check the exact signature header name TikTok sends for your webhook event type and that verify() reads it
  4. Exclude the webhook route from body-rewriting middleware/proxies

Example fix

// before
const body = JSON.stringify(request.body)
const ok = verifySignature(body, request.headers['signature'], secret)
// after
const raw = request.rawBody // captured pre-parse
const ok = raw ? verifySignature(raw, request.headers['signature'], secret) : false
Defensive patterns

Strategy: validation

Validate before calling

const expected = crypto.createHmac('sha256', process.env.TIKTOK_CLIENT_SECRET!).update(req.rawBody).digest('hex')
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(String(req.headers['signature'] ?? '')))) {
  return res.status(401).json({ status: 'invalid_signature' })
}

Type guard

function hasValidTikTokSignature(req: Request): boolean {
  const sig = req.headers['signature']
  return typeof sig === 'string' && Buffer.isBuffer((req as any).rawBody) && verifyHmac((req as any).rawBody, sig, secret)
}

Try / catch

if (!hasValidTikTokSignature(req)) {
  return res.status(401).json({ status: 'invalid_signature' })
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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