ComposioHQ/composio · error · ComposioWebhookSignatureVerificationError
The webhook timestamp is outside the allowed tolerance. The
Error message
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. What it means
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.
Source
Thrown at ts/packages/core/src/models/Triggers.ts:1314
/**
* 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.`
);
}
const webhookTimeMs = timestampSeconds * 1000;
const currentTime = Date.now();
const timeDifference = Math.abs(currentTime - webhookTimeMs);
if (timeDifference > tolerance * 1000) {
throw new ComposioWebhookSignatureVerificationError(
`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.`
);
}
}
}
View on GitHub (pinned to 64b1b85502)
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
Example fix
// before
verifyWebhookSignature(payload, sig, { secret, webhookId, webhookTimestamp }); // default tolerance too small for delayed replay
// after
verifyWebhookSignature(payload, sig, {
secret,
webhookId,
webhookTimestamp,
tolerance: 300, // seconds
}); Defensive patterns
Strategy: validation
Validate before calling
const ts = Number(req.headers['webhook-timestamp']);
const age = Math.abs(Date.now() / 1000 - ts);
if (age > TOLERANCE_SECONDS) return res.status(400).send('Webhook too old'); Type guard
null
Try / catch
try { verifyWebhookSignature(..., { tolerance: 300 }); } catch (e) { if (e instanceof ComposioWebhookSignatureVerificationError && /tolerance/.test(e.message)) return res.status(400).end(); throw e; } Prevention
- Pass an explicit tolerance in seconds
- Keep server clocks NTP-synced
- Process webhooks immediately; don't verify stale queued requests with tight tolerance
When it happens
Trigger: 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).
Common situations: Replaying captured curl requests in debugging, queued/delayed webhook processing, NTP drift on the host, or copying test fixtures with hardcoded old timestamps.
Related errors
- Invalid parameters passed to set webhook subscription
- No webhook ID was provided. Please pass the value of the 'we
- No webhook timestamp was provided. Please pass the value of
- No valid v1 signature found in the webhook-signature header.
- The signature provided is invalid. Please ensure you are usi
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/5f716b6f72c23e8b.
Report an issue: GitHub.