{"record":{"id":"13dc06df60c68ea2","repo":"ComposioHQ/composio","slug":"invalid-webhook-timestamp-webhooktimestamp-ex","errorCode":null,"errorMessage":"Invalid webhook timestamp: ${webhookTimestamp}. Expected Unix timestamp in seconds.","messagePattern":"Invalid webhook timestamp: (.+?)\\. Expected Unix timestamp in seconds\\.","errorType":"exception","errorClass":"ComposioWebhookPayloadError","httpStatus":null,"severity":"error","filePath":"ts/packages/core/src/models/Triggers.ts","lineNumber":1304,"sourceCode":"      }\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\n    const webhookTimeMs = timestampSeconds * 1000;\n    const currentTime = Date.now();\n    const timeDifference = Math.abs(currentTime - webhookTimeMs);\n\n    if (timeDifference > tolerance * 1000) {\n      throw new ComposioWebhookSignatureVerificationError(\n        `The webhook timestamp is outside the allowed tolerance. ` +\n          `The webhook was sent ${Math.round(timeDifference / 1000)} seconds ago, ` +\n          `but the maximum allowed age is ${tolerance} seconds.`\n      );\n    }\n  }\n}\n","sourceCodeStart":1286,"sourceCodeEnd":1322,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/ts/packages/core/src/models/Triggers.ts#L1286-L1322","documentation":"The 'webhook-timestamp' header could not be parsed as an integer Unix timestamp in seconds (parseInt yields NaN). The timestamp is needed both for the HMAC payload and for the replay-tolerance check, so a non-numeric value is rejected with ComposioWebhookPayloadError before any crypto work.","triggerScenarios":"Passing a timestamp like '2024-01-01T00:00:00Z', an ISO string, milliseconds with a trailing unit ('1700000000000ms'), an empty-ish/garbage string that survived the earlier length check, or a value corrupted by header encoding.","commonSituations":"Test fixtures using ISO dates instead of epoch seconds, transforming the header before verification, or non-Composio clients sending arbitrary header values to your endpoint.","solutions":["Pass the header value through unmodified — Composio always sends epoch seconds as a string","In tests, generate Math.floor(Date.now()/1000).toString()","Return 400 early for requests whose webhook-timestamp is not a digit-only string","Check for middleware that rewrites header values"],"exampleFix":"// before\nconst ts = new Date().toISOString(); // wrong format\n// after\nconst ts = Math.floor(Date.now() / 1000).toString();","handlingStrategy":"validation","validationCode":"const ts = String(req.headers['webhook-timestamp'] ?? '');\nif (!/^\\d+$/.test(ts)) return res.status(400).send('Bad webhook-timestamp');","typeGuard":"const isUnixSeconds = (v: unknown): v is string => typeof v === 'string' && /^\\d{10}$/.test(v);","tryCatchPattern":"try { verifyWebhookSignature(...); } catch (e) { if (e instanceof ComposioWebhookPayloadError) return res.status(400).end(); throw e; }","preventionTips":["Pass header values through unmodified","Use epoch-seconds strings in fixtures","Early-reject non-digit timestamp headers"],"tags":["webhook","timestamp","payload-validation","typescript"],"backgroundTag":"webhook-payload-validation-failed","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}