Security & Verification
Verify incoming Trasys webhook requests are authentic using HMAC-SHA256 signatures and timestamp headers, in the same format Stripe webhooks already use.
Every webhook request Trasys sends includes two extra headers:
X-Trasys-Signature: sha256=<hmac-hex>
X-Trasys-Timestamp: <unix-seconds>How signatures work
- Trasys builds a signed string:
{timestamp}.{raw-body} - It computes
HMAC-SHA256(secret, signed-string) - The hex digest is sent as
X-Trasys-Signature: sha256=<hex>
This is the same format as Stripe webhooks, so existing middleware often works directly.
Verifying in Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyWebhook(rawBody, headers, secret) {
const signature = headers['x-trasys-signature'];
const timestamp = headers['x-trasys-timestamp'];
if (!signature || !timestamp) return false;
// Reject stale events (older than 5 minutes)
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (age > 300) return false;
const expected = 'sha256=' + createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
try {
return timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
} catch {
return false;
}
}
// Express example — use raw body parser!
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const valid = verifyWebhook(req.body, req.headers, process.env.WEBHOOK_SECRET);
if (!valid) return res.status(401).json({ error: 'Invalid signature' });
res.sendStatus(200);
const event = JSON.parse(req.body);
handleEvent(event);
});Use express.raw() or equivalent, not express.json(). Signature verification requires the exact raw bytes of the body. Re-serialising parsed JSON can change whitespace and invalidate the HMAC.
Verifying in Python
import hashlib, hmac, time
def verify_webhook(raw_body: bytes, headers: dict, secret: str) -> bool:
signature = headers.get("x-trasys-signature", "")
timestamp = headers.get("x-trasys-timestamp", "")
if not signature or not timestamp:
return False
# Reject events older than 5 minutes
if abs(time.time() - float(timestamp)) > 300:
return False
signed = f"{timestamp}.{raw_body.decode()}"
expected = "sha256=" + hmac.new(
secret.encode(), signed.encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)Replay protection
The X-Trasys-Timestamp header is a Unix timestamp (seconds). You should reject any event where the timestamp is more than 5 minutes in the past or future to protect against replay attacks.
Rotating your secret
Webhook secrets cannot be retrieved after creation — if you lose yours, delete the endpoint and recreate it. A new secret will be shown once.
| Header | Format | Notes |
|---|---|---|
X-Trasys-Signature | sha256=<64-char-hex> | Always verify this |
X-Trasys-Timestamp | Unix seconds (string) | Reject if ` |
X-Trasys-Event | Event type string | Informational only |
Event Types
Every webhook event Trasys can deliver, including alert.fired, incident.created, incident.updated, and incident.resolved, with full JSON payload schemas.
Delivery Logs & Retries
Understand the webhook delivery log, Trasys's two-attempt retry policy with a 60-second backoff, and how to debug and re-test failed webhook deliveries.

