yikart/AiToEarn · error

invalid_signature

Error message

invalid_signature

What it means

TikTok webhook provider returns a plain JSON 401 { status: 'invalid_signature' } when the request's signature header fails HMAC verification against the configured secret. The event is discarded rather than processed.

Source

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

@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) {
      return false

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Confirm the configured TikTok client secret matches the app that sends the webhooks
  2. Ensure rawBody is captured verbatim (no re-serialization) before verification
  3. Check any intermediary services preserve headers and body bytes
  4. Rotate/re-register the webhook in the TikTok developer console after secret changes

Example fix

// before
app.use(express.json()) // consumes body, rawBody lost
// after
app.use(express.json({ verify: (req, _res, buf) => { (req as any).rawBody = buf } }))
Defensive patterns

Strategy: validation

Validate before calling

const sig = req.headers['x-tiktok-signature'] ?? req.headers['signature']
if (!sig || !rawBody || !process.env.TIKTOK_CLIENT_SECRET) throw new Error('missing TikTok signature inputs')
const expected = createHmac('sha256', process.env.TIKTOK_CLIENT_SECRET).update(rawBody).digest('hex')
if (!timingSafeEqual(Buffer.from(expected), Buffer.from(String(sig)))) throw new Error('TikTok signature mismatch')

Type guard

function hasTikTokSignature(sig: unknown): sig is string {
  return typeof sig === 'string' && sig.length > 0
}

Prevention

When it happens

Trigger: POST to the TikTok webhook endpoint with missing/invalid signature header, missing rawBody, or an unset/incorrect clientSecret used by verify().

Common situations: TikTok app secret changed or misconfigured in env; payload proxied through a service that re-encodes the body; encrypted payloads signed with a different key than configured; scanners posting unsigned junk to the public URL.

Related errors


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