Webhook Security
Always verify webhook signatures before processing payloads. This confirms the request came from Ttoolab and was not tampered with.
Request headers
Section titled “Request headers”Every webhook delivery includes these headers:
| Header | Description |
| :----- | :---------- |
| X-Ttoolab-Event-Id | Unique event ID (UUID) |
| X-Ttoolab-Event-Type | Event type (e.g. experiment.conversion) |
| X-Ttoolab-Timestamp | Unix timestamp (seconds) when the request was signed |
| X-Ttoolab-Signature | HMAC-SHA256 hex digest |
| Content-Type | application/json |
| User-Agent | Ttoolab-Webhooks/1.0 |
Webhook secret
Section titled “Webhook secret”When you create a webhook, Ttoolab generates a secret with the format:
whsec_abc123...Store this secret securely on your server. Do not commit it to version control or expose it in client-side code.
Signature verification
Section titled “Signature verification”Ttoolab signs the concatenation of the timestamp and raw request body:
signature = HMAC-SHA256(secret, timestamp + body)Node.js example
Section titled “Node.js example”import crypto from "crypto";
function verifyWebhook(secret, timestamp, body, signature) { const expected = crypto .createHmac("sha256", secret) .update(`${timestamp}${body}`) .digest("hex");
return crypto.timingSafeEqual( Buffer.from(expected, "hex"), Buffer.from(signature, "hex") );}
// In your HTTP handler:const timestamp = req.headers["x-ttoolab-timestamp"];const signature = req.headers["x-ttoolab-signature"];const rawBody = req.rawBody; // must be the exact bytes received
if (!verifyWebhook(WEBHOOK_SECRET, timestamp, rawBody, signature)) { return res.status(401).send("Invalid signature");}Python example
Section titled “Python example”import hmacimport hashlib
def verify_webhook(secret: str, timestamp: str, body: str, signature: str) -> bool: expected = hmac.new( secret.encode(), f"{timestamp}{body}".encode(), hashlib.sha256, ).hexdigest() return hmac.compare_digest(expected, signature)Replay attack prevention
Section titled “Replay attack prevention”Reject requests with timestamps too far from the current time:
const TOLERANCE_SECONDS = 300; // 5 minutesconst now = Math.floor(Date.now() / 1000);const ts = parseInt(timestamp, 10);
if (Math.abs(now - ts) > TOLERANCE_SECONDS) { return res.status(401).send("Timestamp too old");}Also implement idempotency with X-Ttoolab-Event-Id to ignore duplicate deliveries.
HTTPS only
Section titled “HTTPS only”Configure webhook URLs with https:// endpoints. Ttoolab will not send secrets to non-HTTPS URLs in production.
Rotate secrets
Section titled “Rotate secrets”If a secret is compromised:
- Create a new webhook with a new secret in the dashboard.
- Update your server configuration.
- Disable or delete the old webhook.