Webhook Signature Verification
VeriSwarm signs every webhook delivery with HMAC-SHA256 so you can verify the payload was not tampered with in transit.
Headers
Every webhook delivery includes:
| Header | Description |
|---|---|
X-VeriSwarm-Signature |
HMAC-SHA256 hex digest of the request body |
X-VeriSwarm-Timestamp |
Unix timestamp (seconds) when the webhook was sent |
X-VeriSwarm-Delivery-Id |
Unique delivery ID for idempotency |
Verification Steps
- Read the
X-VeriSwarm-SignatureandX-VeriSwarm-Timestampheaders - Concatenate
{timestamp}.{raw_body}(the timestamp, a dot, and the raw request body bytes) - Compute HMAC-SHA256 using your webhook signing secret as the key
- Compare the computed hex digest with the signature header (use constant-time comparison)
- Optionally check the timestamp is within 5 minutes to prevent replay attacks
Python Example
import hmac
import hashlib
import time
def verify_webhook(raw_body: bytes, signature: str, timestamp: str, secret: str) -> bool:
# Check timestamp freshness (optional but recommended)
if abs(time.time() - int(timestamp)) > 300:
return False # Older than 5 minutes
# Compute expected signature
message = f"{timestamp}.{raw_body.decode('utf-8')}".encode("utf-8")
expected = hmac.new(
key=secret.encode("utf-8"),
msg=message,
digestmod=hashlib.sha256,
).hexdigest()
# Constant-time comparison
return hmac.compare_digest(expected, signature)
# Usage in Flask
@app.route("/webhooks/veriswarm", methods=["POST"])
def handle_veriswarm_webhook():
raw_body = request.get_data()
signature = request.headers.get("X-VeriSwarm-Signature", "")
timestamp = request.headers.get("X-VeriSwarm-Timestamp", "")
if not verify_webhook(raw_body, signature, timestamp, WEBHOOK_SECRET):
return "Invalid signature", 401
payload = request.get_json()
# Process the event...
return "OK", 200
Node.js Example
const crypto = require("crypto");
function verifyWebhook(rawBody, signature, timestamp, secret) {
// Check timestamp freshness
if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
return false;
}
const message = `${timestamp}.${rawBody}`;
const expected = crypto
.createHmac("sha256", secret)
.update(message)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(signature, "hex")
);
}
// Usage in Express
app.post("/webhooks/veriswarm", express.raw({ type: "*/*" }), (req, res) => {
const rawBody = req.body.toString();
const signature = req.headers["x-veriswarm-signature"] || "";
const timestamp = req.headers["x-veriswarm-timestamp"] || "";
if (!verifyWebhook(rawBody, signature, timestamp, process.env.WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const payload = JSON.parse(rawBody);
// Process the event...
res.status(200).send("OK");
});
Webhook Event Types
| Event | Description |
|---|---|
decision.checked |
A trust decision was made (includes agent_id, decision, reason_code) |
provider.report.ingested |
An event/report was ingested |
agent.killed |
An agent's kill switch was activated (Guard) |
agent.verified |
An agent's identity was verified (Passport) |
Testing
Use the webhook test endpoint to send a test delivery to your configured URL:
curl -X POST https://api.veriswarm.ai/v1/public/providers/webhooks/{webhook_id}/test \
-H "x-account-access-token: YOUR_TOKEN"
Retry Policy
Failed deliveries are retried with exponential backoff:
- Attempt 1: Immediate
- Attempt 2: 30 seconds
- Attempt 3: 2 minutes
- Attempt 4: 5 minutes
- Final: Marked as exhausted, alert sent to admin