hcengineering/platform · error
Invalid hook token
Error message
Invalid hook token
What it means
The MTA webhook handler in pod-mail-worker validates an incoming webhook by comparing the 'x-hook-token' request header against the configured hookToken (config.hookToken). When the token is configured and the header is missing, malformed, or does not exactly match, the handler rejects the request with 'Invalid hook token' to prevent unauthorized email hook injection. It is a security gate, not an internal fault.
Source
Thrown at services/mail/pod-mail-worker/src/handlerMta.ts:41
isHulyMessage,
generateNewEmailId,
MailHeader
} from '@hcengineering/mail-common'
import { getClient as getAccountClient } from '@hcengineering/account-client'
import { createRestTxOperations } from '@hcengineering/api-client'
import { mailServiceToken, baseConfig, kvsClient } from './client'
import config from './config'
import { MtaMessage, HulyMessageType } from './types'
import { getHeader, parseContent } from './utils'
import { decodeEncodedWords } from './decode'
export async function handleMtaHook (req: Request, res: Response, ctx: MeasureContext): Promise<void> {
try {
if (config.hookToken !== undefined) {
const token = req.headers['x-hook-token']
if (token !== config.hookToken) {
throw new Error('Invalid hook token')
}
}
const mta: MtaMessage = req.body
const headers: string[] = mta.message.headers.map((header) => header[0].trim()) ?? []
if (isHulyMessage(headers)) {
return
}
const from: EmailContact = getEmailContact(mta.envelope.from.address)
if (config.ignoredAddresses.includes(from.email)) {
return
}
const fromHeader = getHeader(mta, MailHeader.From)
if (fromHeader !== undefined) {
const { firstName, lastName } = extractContactName(ctx, fromHeader, from.email)
from.firstName = firstNameView on GitHub (pinned to 63e28dc964)
Solutions
- Set the 'x-hook-token' header on the MTA webhook request to exactly match config.hookToken (HOOK_TOKEN env var)
- If tokens were rotated, redeploy/reconfigure the MTA side with the new token
- If you intend no auth (dev only), unset config.hookToken so the check is skipped
- Log/inspect the received header value (carefully, no secrets in logs) to confirm exact match, including whitespace
Example fix
// before
curl -X POST https://mta-hook/url -d '{...}'
// after
curl -X POST https://mta-hook/url -H "x-hook-token: $HOOK_TOKEN" -d '{...}' Defensive patterns
Strategy: validation
Validate before calling
function validateHookRequest(req: Request, expectedToken?: string): void {
if (expectedToken !== undefined && req.headers['x-hook-token'] !== expectedToken) {
throw new Error('Invalid hook token')
}
} Type guard
function hasValidHookToken(req: Request, config: { hookToken?: string }): boolean {
return config.hookToken === undefined || req.headers['x-hook-token'] === config.hookToken
} Try / catch
try {
await handleMtaHook(req, res, ctx)
} catch (e) {
if (e.message === 'Invalid hook token') {
res.status(401).send('unauthorized')
} else throw e
} Prevention
- Keep HOOK_TOKEN in one shared secret store referenced by both MTA and worker so rotation is atomic
- Configure the MTA client template to always send the x-hook-token header
- Use constant-time comparison for token checks in security-sensitive deployments
- Smoke-test the webhook after every deployment
When it happens
Trigger: A POST to the MTA hook endpoint with: (1) no 'x-hook-token' header, (2) a token that differs from config.hookToken (typo, stale secret after rotation), or (3) the worker redeployed with a new HOOK_TOKEN env value while the MTA still sends the old one.
Common situations: Rotating the hook token in one deployment (worker or MTA) but not the other; token passed via query param instead of header; multiple MTA instances where only some were updated; local dev MTA not configured to send the header at all while the worker sets config.hookToken.
Related errors
- Token revoked
- Invalid signature
- Invalid signature
- Server secret not set, datalake storage adapter initialized
- Server secret not set, hulylake storage adapter initialized
AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29).
Data as JSON: /api/errors/bb29afd79ab4c350.
Report an issue: GitHub.