ComposioHQ/composio · critical · ComposioWebhookSignatureVerificationError

The signature provided is invalid. Please ensure you are usi

Error message

The signature provided is invalid. Please ensure you are using the correct webhook secret.

What it means

The computed HMAC-SHA256(msgId.timestamp.payload, secret) does not match any of the v1 signatures supplied in the 'webhook-signature' header. This means the payload was signed with a different secret, or the payload/id/timestamp/secret inputs differ from what was signed. It is the core anti-tamper check of webhook verification.

Source

Thrown at ts/packages/core/src/models/Triggers.ts:1290

          "Expected format: 'v1,base64EncodedSignature'"
      );
    }

    // Compute expected signature: HMAC-SHA256(msgId.timestamp.payload, secret) -> base64
    const toSign = `${webhookId}.${webhookTimestamp}.${payload}`;
    const expectedSignature = await hmacSha256Base64(secret, toSign);

    // Check if any of the provided signatures match
    let isValid = false;
    for (const providedSignature of v1Signatures) {
      if (timingSafeEqual(providedSignature, expectedSignature)) {
        isValid = true;
        break;
      }
    }

    if (!isValid) {
      throw new ComposioWebhookSignatureVerificationError(
        'The signature provided is invalid. Please ensure you are using the correct webhook secret.'
      );
    }
  }

  /**
   * Validates that the webhook timestamp is within the allowed tolerance
   * @private
   */
  private validateWebhookTimestamp(webhookTimestamp: string, tolerance: number): void {
    const timestampSeconds = parseInt(webhookTimestamp, 10);

    if (Number.isNaN(timestampSeconds)) {
      throw new ComposioWebhookPayloadError(
        `Invalid webhook timestamp: ${webhookTimestamp}. Expected Unix timestamp in seconds.`
      );
    }

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Re-copy the webhook secret from the Composio dashboard and confirm it matches the environment variable used
  2. Verify against the RAW request body bytes (e.g. express.raw before JSON parsing), not a re-serialized object
  3. Ensure webhookId and webhookTimestamp come from the same delivery as the payload
  4. Confirm you are not mixing secrets across workspaces/environments

Example fix

// before (express, body already parsed)
app.post('/webhook', express.json(), (req, res) => {
  verifyWebhookSignature(JSON.stringify(req.body), ...);
});
// after (raw body)
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
  verifyWebhookSignature(req.body.toString('utf8'), req.headers['webhook-signature'], {
    secret: process.env.COMPOSIO_WEBHOOK_SECRET,
    webhookId: req.headers['webhook-id'],
    webhookTimestamp: req.headers['webhook-timestamp'],
  });
});
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try {
  verifyWebhookSignature(rawBody, sig, { secret, webhookId, webhookTimestamp });
} catch (e) {
  if (e instanceof ComposioWebhookSignatureVerificationError) {
    // Do NOT process the payload; 400/401 and alert — possible forgery or secret drift
    return res.status(401).end();
  }
  throw e;
}

Prevention

When it happens

Trigger: Using the wrong webhook secret (regenerated in the dashboard, wrong environment/project key), verifying a modified or re-serialized body (JSON re-stringified so bytes differ), mixing the id/timestamp from one delivery with the body of another, or receiving a forged/spoofed request.

Common situations: Rotating the webhook secret in the dashboard without updating the env var, verifying req.body (parsed then re-stringified) instead of the raw request body, secret copied with whitespace/quotes, or multiple Composio workspaces with different secrets.

Related errors


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/e051f9d0b25476a7. Report an issue: GitHub.