yikart/AiToEarn · warning
-1
-1
Error message
ChannelWebhookInvalidSignature
What it means
The Bilibili webhook provider rejects incoming webhook HTTP requests whose signature verification fails by responding 401 with code -1 and message ChannelWebhookInvalidSignature. The provider's verify(request) checked the request signature against the configured Bilibili webhook secret and it did not match, so the payload is not processed.
Solutions
- Confirm the webhook secret configured on the server matches the one registered with Bilibili
- Ensure signature verification reads the exact raw request body (no proxy re-serialization); configure the proxy to pass the body untouched
- Check that the correct environment's secret/env var is loaded (aitoearn.cn vs aitoearn.ai)
- Re-register the webhook URL/secret on the Bilibili side if it was rotated
Example fix
// before
// secret loaded from generic env
this.secret = process.env.WEBHOOK_SECRET
// after
// platform/env specific secret with startup validation
this.secret = process.env.BILIBILI_WEBHOOK_SECRET
if (!this.secret)
throw new Error('BILIBILI_WEBHOOK_SECRET is required') Defensive patterns
Strategy: validation
Validate before calling
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
if (req.headers['x-signature'] !== expected) return res.status(401).json({ code: -1 }) Type guard
function hasValidSignature(header: string | undefined, raw: Buffer, secret: string): boolean {
if (!header) return false
return crypto.timingSafeEqual(Buffer.from(header), crypto.createHmac('sha256', secret).update(raw).digest())
} Try / catch
app.post('/webhooks/bilibili', express.raw({ type: 'application/json' }), (req, res) => {
if (!hasValidSignature(req.headers['x-signature'], req.body, secret)) {
return res.status(401).json({ code: -1, message: 'ChannelWebhookInvalidSignature' })
}
// process payload
}) Prevention
- Store the Bilibili webhook secret in env per environment and validate at startup
- Configure proxies to pass the raw body byte-for-byte to the webhook route
- Rotate the secret on both Bilibili and server config simultaneously
- Log signature presence (not value) on failures for diagnostics
When it happens
Trigger: POST to the Bilibili webhook endpoint where the signature header is missing, computed with a different secret than the server's config, or the raw body was re-serialized (altering bytes) before HMAC verification.
Common situations: Webhook secret rotated on Bilibili's side but not in server env config; a proxy/gateway re-encoding the JSON body so the signed bytes differ; misconfigured per-environment secret (cn vs intl); replayed or forged requests.
Related errors
- -1
- ChannelWebhookInvalidSignature
- ChannelWebhookInvalidSignature
- ChannelWebhookInvalidSignature
- ChannelWebhookInvalidSignature
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/086b746c4b96b27f.
Report an issue: GitHub.
Appendix: source
Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/bilibili/bilibili-webhook.provider.ts:41
} from './bilibili.interface'
type PersistedPublishDataOption = PublishRecord['dataOption']
@Injectable()
export class BilibiliWebhookProvider implements PlatformWebhookHandler {
private readonly logger = new Logger(BilibiliWebhookProvider.name)
constructor(
private readonly config: BilibiliConfig,
@Optional() private readonly publishRecordRepo?: PublishRecordRepository,
@Optional() private readonly stateService?: PublishStateService,
) {}
async handle(request: Request, response: Response): Promise<void> {
const verified = this.verify(request)
this.logger.log({ platform: AccountType.Bilibili, verified }, 'Bilibili webhook received')
if (!verified) {
response.status(401).json({ code: -1, message: getCodeMessage(ResponseCode.ChannelWebhookInvalidSignature, undefined, getLocale()) })
return
}
const body = this.parseBilibiliBody(request.body)
if (!body) {
response.status(200).json({ code: 0, message: 'ok' })
return
}
if (body.event === BilibiliWebhookEvent.VerifyWebhooks) {
response.status(200).json({ data: body.content.data })
return
}
await this.applyBilibiliPublishResult(body)
response.status(200).json({ code: 0, message: 'ok' })
}
private verify(request: RawBodyRequest): boolean {View on GitHub (pinned to d3aa8bea5b)