{"record":{"id":"5f716b6f72c23e8b","repo":"ComposioHQ/composio","slug":"the-webhook-timestamp-is-outside-the-allowed-toler","errorCode":null,"errorMessage":"The webhook timestamp is outside the allowed tolerance. The webhook was sent ${Math.round(timeDifference / 1000)} seconds ago, but the maximum allowed age is ${tolerance} seconds.","messagePattern":"The webhook timestamp is outside the allowed tolerance\\. The webhook was sent (.+?) seconds ago, but the maximum allowed age is (.+?) seconds\\.","errorType":"exception","errorClass":"ComposioWebhookSignatureVerificationError","httpStatus":null,"severity":"error","filePath":"ts/packages/core/src/models/Triggers.ts","lineNumber":1314,"sourceCode":"  /**\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":1296,"sourceCodeEnd":1322,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/ts/packages/core/src/models/Triggers.ts#L1296-L1322","documentation":"The webhook's Unix timestamp differs from the server clock by more than the configured tolerance (in seconds; the comparison is tolerance*1000 ms). This is a replay-attack mitigation: stale or far-future signatures are rejected even if the HMAC itself is valid.","triggerScenarios":"Replaying an old (captured) webhook request, verifying a recorded webhook during development after delay exceeds tolerance, a server clock skewed by more than the tolerance, or a tolerance set too small (e.g. 0/undefined coerced to a tiny value).","commonSituations":"Replaying captured curl requests in debugging, queued/delayed webhook processing, NTP drift on the host, or copying test fixtures with hardcoded old timestamps.","solutions":["If replaying captured requests in dev, refresh the timestamp/signature or raise the tolerance explicitly","Check server clock sync (NTP) if genuine deliveries are being rejected","Pass an explicit, sensible tolerance (seconds) when calling verification","Process webhooks promptly instead of enqueueing raw headers for much later verification"],"exampleFix":"// before\nverifyWebhookSignature(payload, sig, { secret, webhookId, webhookTimestamp }); // default tolerance too small for delayed replay\n// after\nverifyWebhookSignature(payload, sig, {\n  secret,\n  webhookId,\n  webhookTimestamp,\n  tolerance: 300, // seconds\n});","handlingStrategy":"validation","validationCode":"const ts = Number(req.headers['webhook-timestamp']);\nconst age = Math.abs(Date.now() / 1000 - ts);\nif (age > TOLERANCE_SECONDS) return res.status(400).send('Webhook too old');","typeGuard":"null","tryCatchPattern":"try { verifyWebhookSignature(..., { tolerance: 300 }); } catch (e) { if (e instanceof ComposioWebhookSignatureVerificationError && /tolerance/.test(e.message)) return res.status(400).end(); throw e; }","preventionTips":["Pass an explicit tolerance in seconds","Keep server clocks NTP-synced","Process webhooks immediately; don't verify stale queued requests with tight tolerance"],"tags":["webhook","replay-protection","clock-skew","tolerance","typescript"],"backgroundTag":"webhook-replay-tolerance-exceeded","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}