Skip to content

Webhook Security

Always verify webhook signatures before processing payloads. This confirms the request came from Ttoolab and was not tampered with.

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 |

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.

Ttoolab signs the concatenation of the timestamp and raw request body:

signature = HMAC-SHA256(secret, timestamp + body)
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");
}
import hmac
import 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)

Reject requests with timestamps too far from the current time:

const TOLERANCE_SECONDS = 300; // 5 minutes
const 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.

Configure webhook URLs with https:// endpoints. Ttoolab will not send secrets to non-HTTPS URLs in production.

If a secret is compromised:

  1. Create a new webhook with a new secret in the dashboard.
  2. Update your server configuration.
  3. Disable or delete the old webhook.