sickn33/agentic-awesome-skills · warning

Webhook verification failed: invalid token

Error message

Webhook verification failed: invalid token

What it means

Logged by handleWebhookVerification when Meta's GET callback verification fails the check mode === 'subscribe' && token === verifyToken && SAFE_CHALLENGE_RE.test(challenge). The handler replies 403 for a bad token/mode and 400 when the token matches but hub.challenge fails the safety regex. It means the WhatsApp Cloud API webhook subscription handshake did not succeed.

Source

Thrown at skills/whatsapp-cloud-api/assets/boilerplate/nodejs/src/webhook-handler.ts:83

export function rawBodyMiddleware(req: Request, _res: Response, buf: Buffer): void {
  (req as any).rawBody = buf;
}

/**
 * Handler de verificacao do webhook (GET).
 * A Meta envia um challenge que deve ser retornado para confirmar o endpoint.
 */
export function handleWebhookVerification(verifyToken: string) {
  return (req: Request, res: Response): void => {
    const mode = req.query['hub.mode'] as string;
    const token = req.query['hub.verify_token'] as string;
    const challenge = req.query['hub.challenge'] as string;

    if (mode === 'subscribe' && token === verifyToken && SAFE_CHALLENGE_RE.test(challenge)) {
      console.log('Webhook verified successfully');
      res.type('text/plain').status(200).send(challenge);
    } else {
      console.warn('Webhook verification failed: invalid token');
      res.status(mode === 'subscribe' && token === verifyToken ? 400 : 403).send();
    }
  };
}

/**
 * Extrai mensagens e status updates do payload do webhook.
 */
export function parseWebhookPayload(payload: WebhookPayload): {
  messages: IncomingMessage[];
  statuses: StatusUpdate[];
} {
  const messages: IncomingMessage[] = [];
  const statuses: StatusUpdate[] = [];

  for (const entry of payload.entry || []) {
    for (const change of entry.changes || []) {
      if (change.value.messages) {

View on GitHub (pinned to 58d857988f)

Solutions

  1. Check the response status your handler returned: 403 means token/mode mismatch — fix the token; 400 means the token matched but hub.challenge failed SAFE_CHALLENGE_RE — inspect the incoming challenge for unexpected characters or encoding.
  2. Make the server's VERIFY_TOKEN env var exactly equal to the Verify Token field in Meta App Dashboard > WhatsApp > Configuration.
  3. Ensure VERIFY_TOKEN is set in the deployment environment with no quotes or whitespace (printf '%s' "$VERIFY_TOKEN" | wc -c to check).
  4. Redeploy/restart the Node process after changing the env var, then retry verification in the Meta dashboard.
  5. If a reverse proxy sits in front, confirm it forwards the query string untouched and does not double-encode hub.challenge.

Example fix

// before: token read raw from env, may carry whitespace
const verifyToken = process.env.VERIFY_TOKEN;

// after: trim and fail fast when unset
const verifyToken = (process.env.VERIFY_TOKEN ?? '').trim();
if (!verifyToken) {
  throw new Error('VERIFY_TOKEN env var is not set');
}
Defensive patterns

Strategy: validation

Validate before calling

// Reject malformed verification requests before the handler logic
function isValidVerificationQuery(req: Request): boolean {
  const mode = req.query['hub.mode'];
  const token = req.query['hub.verify_token'];
  const challenge = req.query['hub.challenge'];
  return typeof mode === 'string' &&
    typeof token === 'string' &&
    typeof challenge === 'string' &&
    challenge.length > 0 && challenge.length <= 256;
}

Type guard

function isWebhookVerificationQuery(q: unknown): q is Record<'hub.mode' | 'hub.verify_token' | 'hub.challenge', string> {
  if (typeof q !== 'object' || q === null) return false;
  const r = q as Record<string, unknown>;
  return typeof r['hub.mode'] === 'string' &&
    typeof r['hub.verify_token'] === 'string' &&
    typeof r['hub.challenge'] === 'string';
}

Prevention

When it happens

Trigger: Meta sends GET /webhook?hub.mode=subscribe&hub.verify_token=...&hub.challenge=... when you click 'Verify and save' in the Meta App Dashboard. The warning fires when hub.verify_token differs from the server's VERIFY_TOKEN, when hub.mode is not 'subscribe', or when hub.challenge contains characters rejected by SAFE_CHALLENGE_RE (correct token but 400 response). Any unrelated GET to the webhook URL also triggers it (403).

Common situations: VERIFY_TOKEN env var on the server differs from the Verify Token typed into the Meta dashboard; trailing whitespace or quotes in the env var; a new deployment that lost the env var; a proxy or query parser mangling/encoding hub.challenge so SAFE_CHALLENGE_RE fails and a 400 is returned instead of echoing the challenge.

Understand the failure class

Related errors


AI-assisted analysis of sickn33/agentic-awesome-skills@58d857988f (2026-08-26). Data as JSON: /api/errors/542e437617f21532. Report an issue: GitHub.