{"record":{"id":"e051f9d0b25476a7","repo":"ComposioHQ/composio","slug":"the-signature-provided-is-invalid-please-ensure-y","errorCode":null,"errorMessage":"The signature provided is invalid. Please ensure you are using the correct webhook secret.","messagePattern":"The signature provided is invalid\\. Please ensure you are using the correct webhook secret\\.","errorType":"exception","errorClass":"ComposioWebhookSignatureVerificationError","httpStatus":null,"severity":"critical","filePath":"ts/packages/core/src/models/Triggers.ts","lineNumber":1290,"sourceCode":"          \"Expected format: 'v1,base64EncodedSignature'\"\n      );\n    }\n\n    // Compute expected signature: HMAC-SHA256(msgId.timestamp.payload, secret) -> base64\n    const toSign = `${webhookId}.${webhookTimestamp}.${payload}`;\n    const expectedSignature = await hmacSha256Base64(secret, toSign);\n\n    // Check if any of the provided signatures match\n    let isValid = false;\n    for (const providedSignature of v1Signatures) {\n      if (timingSafeEqual(providedSignature, expectedSignature)) {\n        isValid = true;\n        break;\n      }\n    }\n\n    if (!isValid) {\n      throw new ComposioWebhookSignatureVerificationError(\n        'The signature provided is invalid. Please ensure you are using the correct webhook secret.'\n      );\n    }\n  }\n\n  /**\n   * Validates that the webhook timestamp is within the allowed tolerance\n   * @private\n   */\n  private validateWebhookTimestamp(webhookTimestamp: string, tolerance: number): void {\n    const timestampSeconds = parseInt(webhookTimestamp, 10);\n\n    if (Number.isNaN(timestampSeconds)) {\n      throw new ComposioWebhookPayloadError(\n        `Invalid webhook timestamp: ${webhookTimestamp}. Expected Unix timestamp in seconds.`\n      );\n    }\n","sourceCodeStart":1272,"sourceCodeEnd":1308,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/ts/packages/core/src/models/Triggers.ts#L1272-L1308","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-copy the webhook secret from the Composio dashboard and confirm it matches the environment variable used","Verify against the RAW request body bytes (e.g. express.raw before JSON parsing), not a re-serialized object","Ensure webhookId and webhookTimestamp come from the same delivery as the payload","Confirm you are not mixing secrets across workspaces/environments"],"exampleFix":"// before (express, body already parsed)\napp.post('/webhook', express.json(), (req, res) => {\n  verifyWebhookSignature(JSON.stringify(req.body), ...);\n});\n// after (raw body)\napp.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {\n  verifyWebhookSignature(req.body.toString('utf8'), req.headers['webhook-signature'], {\n    secret: process.env.COMPOSIO_WEBHOOK_SECRET,\n    webhookId: req.headers['webhook-id'],\n    webhookTimestamp: req.headers['webhook-timestamp'],\n  });\n});","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  verifyWebhookSignature(rawBody, sig, { secret, webhookId, webhookTimestamp });\n} catch (e) {\n  if (e instanceof ComposioWebhookSignatureVerificationError) {\n    // Do NOT process the payload; 400/401 and alert — possible forgery or secret drift\n    return res.status(401).end();\n  }\n  throw e;\n}","preventionTips":["Always verify the raw request body bytes, not re-serialized JSON","Load the webhook secret from a single env var kept in sync with the dashboard","Rotate the secret with a transition period supporting old and new secrets","Use matched id/timestamp/body from the same delivery"],"tags":["webhook","hmac","secret-mismatch","security","typescript"],"backgroundTag":"webhook-signature-verification-failed","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}